Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Appendix B: When to Use What

B.1 The Short Answer

NeedStructure
Fast lookup by keyHash table
Ordered dataBalanced BST, B+ tree
Range queriesB+ tree, segment tree
Priority accessHeap
LIFOStack
FIFOQueue
Graph traversalAdjacency list
String prefixTrie
Approximate membershipBloom filter
Approximate countingHyperLogLog
2D spatialR-tree, KD-tree
3D spatialOctree

B.2 The Decision, in Four Questions

Most structure choices resolve with these, asked in order.

1. Do I ever need the elements in order?

This is the single most consequential question, and the one most often skipped. Ordered iteration, range queries, “the next key after X”, and “the smallest key” all require a tree or a sorted structure. A hash table can do none of them at any price.

The failure mode is not discovering this on day one. It is discovering it six months in, when someone asks for “all records between these two dates” and the answer requires replacing the container.

Order needed?Go to
No, point lookups onlyHash table
YesBalanced BST (in memory), B+ tree (on disk)
Only by priority, one at a timeHeap
Only by prefixTrie or radix tree

2. What is the read/write mix?

PatternFavors
Read-heavy, rarely changesSorted array, perfect hash, immutable structure
BalancedHash table, balanced BST
Write-heavyLSM tree, append-only log
Append-onlyDynamic array, log

3. Where does it live?

LocationConstraintChoose
CPU cache / smallConstant factors dominateArray, linear scan
RAMPointer chasing costsHash table, B-tree (not BST)
Disk / SSDBlock transfers dominateB+ tree, LSM tree
Across machinesRound trips dominateConsistent hashing, CRDT, DHT

4. How exact must it be?

If an approximate answer is acceptable, the space savings are usually one to five orders of magnitude, but check which way the errors go first (Chapter 14).

B.3 By Operation

The structure that makes each operation cheapest, with the cost of choosing it.

OperationBest choiceCost of that choice
Lookup by keyHash table, O(1)No ordering at all
Lookup by indexArray, O(1)Insertion in the middle is O(n)
Min or maxHeap, O(1)Arbitrary search is O(n)
k-th smallestOrder-statistic tree, O(log n)Extra size field per node
Predecessor / successorBalanced BST, O(log n)Slower than a hash table for point lookups
Range queryB+ tree or segment tree, O(log n + k)Higher write cost
Prefix matchTrie, O(P)Memory per node
Insert at both endsDeque, O(1)No O(1) middle insertion
Insert in the middle given a positionLinked list, O(1)Finding the position is O(n)
Merge two collectionsLeftist / pairing heap, O(log n)Slower than a binary heap otherwise
Membership, huge setBloom filter, O(k)False positives; no enumeration
Count distinctHyperLogLog, O(1)~2% error
Connectivity under mergingUnion-find, O(α(n))Cannot split

B.4 By Scale

The right answer changes with n, and the changes are larger than intuition suggests.

nWhat actually wins
< 100A flat array and a linear scan. One cache line at a time, perfect prefetching, no hashing. Clever structures usually lose here.
10³–10⁶Hash tables, balanced trees. Classic complexity analysis applies cleanly.
10⁶–10⁹Cache and memory layout dominate. Prefer B-trees over BSTs, arrays over pointer chains.
> RAMExternal memory model. B+ trees, LSM trees, memory-mapped files.
> one machineSharding, consistent hashing, replication. Coordination becomes the cost.

B.5 Common Mistakes

Using a linked list because insertion is O(1). It is O(1) only once you already hold the node. Finding it is O(n), and the traversal is cache-hostile. std::vector beats std::list for middle insertion at surprisingly large sizes: measure before believing otherwise.

Using a hash map when you needed order. See B.2, question 1. This is the most expensive mistake on this page because it surfaces late.

Reaching for a fancy structure at small n. A segment tree over 50 elements is slower than a loop, and considerably more code to get wrong.

Ignoring the worst case on untrusted input. A hash table is O(1) average and O(n) adversarial. If users can choose the keys, you need a keyed hash or a treeifying table (Chapter 12).

Optimizing before profiling, and profiling the wrong thing. If a function is slow and the arithmetic is trivial, the problem is memory, and only hardware counters will show it (Chapter 22).

Testing with random data. Real data arrives sorted far more often than random data does: by timestamp, by ID, by insertion order. Sorted input is the worst case for a naive BST and for quicksort with a fixed pivot. Test sorted, reverse-sorted, and all-identical deliberately.

B.6 Language Defaults

What to reach for first, per language, before writing anything custom.

NeedPythonJavaC++GoRust
Hash mapdictHashMapunordered_mapmapHashMap
Ordered mapNone (use sortedcontainers)TreeMapmapn/aBTreeMap
Dynamic arraylistArrayListvectorsliceVec
Dequecollections.dequeArrayDequedequen/aVecDeque
Heapheapq (min only)PriorityQueuepriority_queue (max)container/heapBinaryHeap (max)
SetsetHashSetunordered_setmap[T]struct{}HashSet
Ordered setn/aTreeSetsetn/aBTreeSet

Two traps worth remembering: Python has no ordered map or tree in the standard library. dict preserves insertion order, which is not the same as sorted order. And heapq is min-only while C++ and Rust default to max-heaps, which is a reliable source of inverted-comparator bugs when porting.

For fuller detail on what these are actually implemented as, see Chapter 22; for exact complexities, Appendix A.