Skip to content
Codeloom
DSA

Data Structures Comparison Guide: When to Use What

A comprehensive comparison of data structures — arrays vs linked lists, hash maps vs trees vs tries, heaps vs BSTs, stacks vs queues, sets vs Bloom filters, with decision flowcharts and complexity tables.

·12 min read · By Codeloom
Intermediate 18 min read

What you'll learn

  • When to choose Array vs LinkedList vs Deque
  • When to choose HashMap vs TreeMap vs Trie
  • When to choose Heap vs Sorted Array vs BST
  • When to choose Stack vs Queue vs Priority Queue
  • When to choose Set vs BitSet vs Bloom Filter
  • When to use adjacency list vs adjacency matrix for graphs
  • A decision flowchart for choosing the right data structure

Prerequisites

Data structures decision guide — which DS to use when

Knowing how a data structure works is step one. The real skill is knowing when to use it. In interviews and system design, the first question is always the same: “Which data structure fits this problem?”

This guide gives you head-to-head comparisons, complexity tables, and a decision flowchart. Bookmark it. Come back to it before interviews. Use it as your mental index into the world of data structures.

1. Array vs LinkedList vs Deque

These are the three fundamental sequential containers. Each makes a different trade-off.

When to use each

OperationArrayLinkedListDeque (double-ended queue)
Access by indexO(1)O(n)O(1) (array-based)
Insert/delete at endO(1) amortisedO(1) if tail pointerO(1)
Insert/delete at frontO(n)O(1)O(1)
Insert/delete in middleO(n)O(1) if you have the nodeO(n)
Memory overheadLow (contiguous)High (pointers per node)Medium
Cache performanceExcellentPoor (scattered memory)Good

Array (contiguous in memory): ┌───┬───┬───┬───┬───┐ │ 3 │ 1 │ 4 │ 1 │ 5 │ → CPU cache loves this └───┴───┴───┴───┴───┘

LinkedList (scattered in memory): [3|→] … [1|→] … [4|→] … [1|→] [5|∅] ↑ cache miss ↑ cache miss ↑ cache miss

Array vs LinkedList memory layout

Decision guide

  • Default choice: Array. It is the fastest, simplest, and most cache-friendly.
  • Need O(1) front insertion/deletion: Deque.
  • Need O(1) middle insertion with a pointer to the node: LinkedList.
  • Building a queue: Deque.
  • Implementing LRU cache: LinkedList (for O(1) move-to-front) + HashMap.

Real-world: arrays back 90% of all data structures. Even “linked” structures like trees are often stored in arrays for cache performance (binary heap = array).

2. HashMap vs TreeMap vs Trie

All three map keys to values, but with very different performance profiles.

Comparison table

OperationHashMapTreeMap (Balanced BST)Trie
InsertO(1) avgO(log n)O(L) where L = key length
LookupO(1) avgO(log n)O(L)
DeleteO(1) avgO(log n)O(L)
Find min/maxO(n)O(log n)Depends
Range query (keys in [a,b])O(n)O(log n + k)Depends
Prefix searchO(n)O(n)O(L)
Ordered iterationO(n log n) to sortO(n) in-orderO(total chars)
SpaceO(n)O(n)O(alphabet * total nodes)

Decision guide

  • Need fast lookup by exact key: HashMap. It is the default.
  • Need keys in sorted order or range queries: TreeMap.
  • Need prefix search (“find all words starting with ‘pre’”): Trie.
  • Keys are integers in a small range: plain array (index = key).
# HashMap: O(1) lookup
from collections import defaultdict
word_count = defaultdict(int)
for word in words:
    word_count[word] += 1

# TreeMap equivalent in Python: SortedDict from sortedcontainers
from sortedcontainers import SortedDict
sd = SortedDict()
sd["apple"] = 1
sd["banana"] = 2
# sd.irange("a", "b") → keys in range ["a", "b"]

# Trie: O(L) prefix search
class TrieNode:
    def __init__(self):
        self.children = {}
        self.is_end = False

Interview tip: when someone says “design autocomplete,” the answer is Trie (or a similar prefix structure). When they say “design a cache,” the answer starts with HashMap.

3. Heap vs Sorted Array vs BST

All three maintain some notion of order, but each is optimised for different access patterns.

Comparison table

OperationMin-HeapSorted ArrayBalanced BST
Find min/maxO(1)O(1)O(log n)
InsertO(log n)O(n)O(log n)
Delete min/maxO(log n)O(1) or O(n)O(log n)
Delete arbitraryO(n)O(n)O(log n)
SearchO(n)O(log n)O(log n)
Find k-th elementO(n)O(1)O(log n) with augmentation
SpaceO(n)O(n)O(n)

Decision guide

  • Need the min or max repeatedly: Heap. It is purpose-built for this.
  • Need both min and max: two heaps (min-heap + max-heap), or a balanced BST.
  • Data is static, need k-th element: Sorted array.
  • Need fast insert + delete + search: Balanced BST.
  • Building a priority queue: Heap.
import heapq

# Heap: perfect for "give me the smallest element"
pq = []
heapq.heappush(pq, 5)
heapq.heappush(pq, 2)
heapq.heappush(pq, 8)
print(heapq.heappop(pq))  # 2

# Sorted array: perfect for binary search
arr = [1, 3, 5, 7, 9]
import bisect
idx = bisect.bisect_left(arr, 5)  # O(log n) search

Classic interview problem: “Find the median of a stream” → use two heaps (max-heap for lower half, min-heap for upper half).

4. Stack vs Queue vs Priority Queue

Three containers with different access policies.

PropertyStackQueuePriority Queue
OrderLIFOFIFOBy priority
PushO(1)O(1)O(log n)
PopO(1)O(1)O(log n)
PeekO(1)O(1)O(1)
Use caseUndo, parsing, DFSBFS, schedulingDijkstra, task scheduling

When to use each

  • Stack: problems involving nesting (parentheses, HTML tags), most recent element (undo/redo), DFS (explicit stack), monotonic stack (next greater element).
  • Queue: BFS (level-by-level traversal), task scheduling (FIFO fairness), rate limiting (sliding window of requests).
  • Priority Queue: always need the best/worst element (Dijkstra’s algorithm, merge k sorted lists, event simulation), scheduling by priority (OS task scheduler).

Stack: Queue: Priority Queue: ┌───┐ ┌───────────┐ ┌───┐ │ 3 │ ← top │ 1 2 3 4 5 │ │ 1 │ ← min (always the best) │ 2 │ └───────────┘ │ 3 │ │ 1 │ front → out │ 5 │ └───┘ │ 7 │ top → out └───┘

Stack (LIFO) vs Queue (FIFO) vs Priority Queue (by priority)

5. Set vs BitSet vs Bloom Filter

All three answer the question “is this element in the collection?”

PropertyHashSetBitSetBloom Filter
InsertO(1)O(1)O(k) (k hash functions)
LookupO(1)O(1)O(k)
DeleteO(1)O(1)Not supported
False positivesNoNoYes (tunable)
False negativesNoNoNo
Space for n elementsO(n)O(max_value) bitsO(n) bits (much less than HashSet)
Works withAny hashable typeIntegers in a known rangeAny hashable type

Decision guide

  • Default membership test: HashSet.
  • Elements are integers in [0, N] and N is manageable: BitSet (extremely space-efficient).
  • Space is critical and false positives are acceptable: Bloom Filter.
# HashSet: general purpose
seen = set()
seen.add("apple")
print("apple" in seen)  # True

# BitSet: for integer elements in a known range
class BitSet:
    def __init__(self, size):
        self.bits = [0] * ((size >> 5) + 1)

    def add(self, x):
        self.bits[x >> 5] |= (1 << (x & 31))

    def contains(self, x):
        return bool(self.bits[x >> 5] & (1 << (x & 31)))

bs = BitSet(1000)
bs.add(42)
print(bs.contains(42))  # True
print(bs.contains(43))  # False

Interview example: “Check if a character has been seen” with only lowercase letters → use a 26-bit integer as a BitSet.

6. Graph Representations: Adjacency List vs Matrix

PropertyAdjacency ListAdjacency Matrix
SpaceO(V + E)O(V^2)
Check if edge existsO(degree)O(1)
Iterate neighboursO(degree)O(V)
Add edgeO(1)O(1)
Remove edgeO(degree)O(1)
Best forSparse graphs (E << V^2)Dense graphs (E close to V^2)

Decision guide

  • Most real-world graphs (social networks, road maps): Adjacency list. They are sparse.
  • Dense graphs or need O(1) edge lookup: Adjacency matrix.
  • Weighted edges: Adjacency list with (neighbour, weight) tuples, or matrix with weights as values.
# Adjacency list (most common)
from collections import defaultdict
graph = defaultdict(list)
graph[0].append(1)
graph[0].append(2)
graph[1].append(2)

# Adjacency matrix
n = 5
matrix = [[0] * n for _ in range(n)]
matrix[0][1] = 1  # edge from 0 to 1
matrix[0][2] = 1  # edge from 0 to 2

Comprehensive Comparison Table

Here is every major data structure at a glance:

Data StructureInsertDeleteSearchMin/MaxOrderedSpace
ArrayO(n)*O(n)O(n)O(n)NoO(n)
Sorted ArrayO(n)O(n)O(log n)O(1)YesO(n)
LinkedListO(1)**O(1)**O(n)O(n)NoO(n)
StackO(1)O(1)O(n)O(n)LIFOO(n)
QueueO(1)O(1)O(n)O(n)FIFOO(n)
HashMapO(1)O(1)O(1)O(n)NoO(n)
TreeMap/BSTO(log n)O(log n)O(log n)O(log n)YesO(n)
HeapO(log n)O(log n)O(n)O(1)PartialO(n)
TrieO(L)O(L)O(L)-LexicographicO(AL*n)
Segment TreeO(log n)-O(log n)O(log n)-O(n)
Fenwick TreeO(log n)-O(log n)--O(n)

*Array append is O(1) amortised. **LinkedList O(1) with pointer to node.

The “Which Data Structure?” Decision Flowchart

When you face a new problem, ask these questions in order:

  1. Do I need key-value mapping? ├─ Yes → Need ordering? → Yes → TreeMap │ → No → HashMap └─ No → Continue

  2. Do I need to maintain order of insertion? ├─ LIFO → Stack ├─ FIFO → Queue ├─ By priority → Heap / Priority Queue └─ No specific order → Continue

  3. Do I need fast membership testing? ├─ Elements are small integers → BitSet ├─ Approximate OK → Bloom Filter └─ Exact → HashSet

  4. Do I need sorted data? ├─ Static (no inserts) → Sorted Array ├─ Dynamic → Balanced BST / TreeSet └─ No → Array or LinkedList

  5. Is it a graph problem? ├─ Sparse → Adjacency List └─ Dense → Adjacency Matrix

  6. Need range queries? ├─ Static → Sparse Table (min/max) or Prefix Sum (sum) └─ Dynamic → Segment Tree or Fenwick Tree

Decision flowchart for choosing data structures

Real-World Mapping

Real-world systemPrimary data structureWhy
Database indexB+ TreeO(log n) on disk with high branching factor
In-memory cache (Redis)Hash table + skip listO(1) lookup + O(log n) sorted ops
Search engine indexInverted index (hash map)O(1) term lookup
DNS resolver cacheHash tableO(1) domain lookup
Undo/redo systemStackLIFO matches undo order
Print queueQueueFIFO fairness
AutocompleteTrieO(L) prefix matching
Event schedulerPriority queue (heap)Always process earliest event
Social networkAdjacency list graphSparse connections
Route plannerWeighted graph + DijkstraShortest path

Interview Tips

When the interviewer says… you should think…

  • “Find the k most frequent…” → HashMap (count) + Heap (top k)
  • “Find if a path exists…” → Graph + BFS/DFS
  • “Design an LRU cache…” → HashMap + Doubly LinkedList
  • “Find the median in a stream…” → Two Heaps
  • “Implement autocomplete…” → Trie
  • “Check for balanced parentheses…” → Stack
  • “Find shortest path…” → Graph + BFS (unweighted) or Dijkstra (weighted)
  • “Range sum queries with updates…” → Segment Tree or Fenwick Tree

The meta-strategy

  1. Start with the simplest structure (array, hash map). Most problems can be solved with these.
  2. Upgrade only when needed. Need ordering? Switch from HashMap to TreeMap. Need fast min? Add a Heap.
  3. Combine structures for complex requirements. LRU cache = HashMap + LinkedList. Median of stream = two Heaps.

Recap

Choosing the right data structure is the most impactful decision in any coding problem:

  • Arrays are the default — fast, simple, cache-friendly
  • HashMaps provide O(1) lookup for unordered data
  • TreeMaps/BSTs maintain sorted order with O(log n) operations
  • Heaps give you the min/max in O(1) with O(log n) updates
  • Tries excel at prefix-based operations on strings
  • Stacks, queues, and priority queues enforce specific access orders
  • Graph representations depend on density

The right data structure makes the right algorithm obvious. The wrong data structure makes every algorithm painful.

Next steps

See these data structures in action in real systems: DSA in Real Systems. For interview preparation strategy, see Problem Solving Framework.

Questions or feedback? Email codeloomdevv@gmail.com.