Skip to content

Master Complexity Cheatsheet

Complexity is the one thing you’re expected to know cold, without looking it up mid-interview or mid-contest. This page is the single-place reference: every operation, data structure, and algorithm from this track, with its time and space complexity side by side. Treat it as a lookup table, not a lesson — if a row doesn’t make sense, the phase that taught it is linked from context in earlier pages.

What you’ll learn

  • The real complexity of every common listlist / dictdict / setset / dequedeque / heapqheapq operation — including the ones that silently cost O(n)O(n).
  • Every sorting and searching algorithm from Phase 4, side by side.
  • Every data structure’s core operations, from arrays to segment trees, Fenwick trees, and DSU.
  • The graph algorithm family — BFS, DFS, Dijkstra, Bellman-Ford, Floyd-Warshall, MST, and topological sort.
  • The Big-O growth hierarchy, and a rule of thumb for how large nn can be under a typical time limit.

Common Python operations

Operationlistlistdictdictsetsetdequedequeheapqheapq
Index / key access x[i]x[i]O(1)O(1)O(1)O(1) avgO(n)O(n)
Append / addO(1)O(1) amortizedO(1)O(1) avgO(1)O(1) avgO(1)O(1)O(logn)O(\log n) (heappushheappush)
Append/insert at frontO(n)O(n)O(1)O(1)
Pop from endO(1)O(1)O(1)O(1)
Pop from frontO(n)O(n)O(1)O(1)O(logn)O(\log n) (heappopheappop, smallest only)
Insert / delete at arbitrary indexO(n)O(n)O(1)O(1) avg (del d[k]del d[k])O(1)O(1) avgO(n)O(n)not supported directly
Membership test x in ...x in ...O(n)O(n)O(1)O(1) avgO(1)O(1) avgO(n)O(n)O(n)O(n)
Search for min/maxO(n)O(n)O(n)O(n)O(n)O(n)O(n)O(n)O(1)O(1) (min only, heap[0]heap[0])
Sort (sorted(...)sorted(...), .sort().sort())O(nlogn)O(n \log n)O(nlogn)O(n \log n) on itemsO(nlogn)O(n \log n) on itemsO(nlogn)O(n \log n)O(nlogn)O(n \log n) (heapify + drain)
sorted(list)sorted(list) from scratchBuilding via heapifyheapify is O(n)O(n)

Sorting algorithms

AlgorithmBestAverageWorstSpaceStable?Notes
Timsort (Python’s sortedsorted/.sort().sort())O(n)O(n)O(nlogn)O(n \log n)O(nlogn)O(n \log n)O(n)O(n)YesAdaptive — exploits existing runs of order
Merge SortO(nlogn)O(n \log n)O(nlogn)O(n \log n)O(nlogn)O(n \log n)O(n)O(n)YesGuaranteed O(nlogn)O(n \log n), no worst-case cliff
Quick SortO(nlogn)O(n \log n)O(nlogn)O(n \log n)O(n2)O(n^2)O(logn)O(\log n)NoWorst case needs an adversarial/sorted input plus a bad pivot rule
Heap SortO(nlogn)O(n \log n)O(nlogn)O(n \log n)O(nlogn)O(n \log n)O(1)O(1)NoIn-place, but poor cache locality in practice
Insertion SortO(n)O(n)O(n2)O(n^2)O(n2)O(n^2)O(1)O(1)YesBest-in-class for nearly-sorted or tiny arrays
Selection SortO(n2)O(n^2)O(n2)O(n^2)O(n2)O(n^2)O(1)O(1)NoAlways O(n2)O(n^2) comparisons, minimal writes
Bubble SortO(n)O(n)O(n2)O(n^2)O(n2)O(n^2)O(1)O(1)YesTeaching tool only — never the right production choice
Counting SortO(n+k)O(n + k)O(n+k)O(n + k)O(n+k)O(n + k)O(n+k)O(n + k)Yeskk = range of key values; useless when kk >> nn
Radix SortO(d(n+k))O(d(n + k))O(d(n+k))O(d(n + k))O(d(n+k))O(d(n + k))O(n+k)O(n + k)Yesdd = number of digits/passes
Bucket SortO(n+k)O(n + k)O(n+k)O(n + k)O(n2)O(n^2)O(n)O(n)Yes (with a stable inner sort)Needs a roughly-uniform input distribution

Searching

TechniqueTimeSpaceRequires
Linear searchO(n)O(n)O(1)O(1)Nothing
Binary search (exact match)O(logn)O(\log n)O(1)O(1)Sorted array
lower_boundlower_bound / upper_boundupper_bound (bisectbisect)O(logn)O(\log n)O(1)O(1)Sorted array
Binary search on the answerO(f(n)log(range))O(f(n) \log(\text{range}))Depends on feasible()feasible()Monotonic feasibility
Hash-based lookup (dictdict/setset)O(1)O(1) average, O(n)O(n) worstO(n)O(n)Hashable keys
Two pointers / sliding windowO(n)O(n)O(1)O(1)Sorted or monotonic structure

Core data structures

StructureAccessSearchInsertDeleteSpaceNotes
Array / Python listlistO(1)O(1)O(n)O(n)O(n)O(n) worst (end: O(1)O(1) amortized)O(n)O(n)O(n)O(n)Contiguous, cache-friendly
Singly/doubly linked listO(n)O(n)O(n)O(n)O(1)O(1) at a known nodeO(1)O(1) at a known nodeO(n)O(n)No random access; cheap splicing
Stack (listlist as stack)O(n)O(n)O(n)O(n)O(1)O(1) (appendappend)O(1)O(1) (poppop)O(n)O(n)LIFO — both ops at the same end
Queue (collections.dequecollections.deque)O(n)O(n)O(n)O(n)O(1)O(1) (appendappend)O(1)O(1) (popleftpopleft)O(n)O(n)FIFO — never use list.pop(0)list.pop(0) here
Hash table (dictdict/setset)O(1)O(1) avg, O(n)O(n) worstO(1)O(1) avgO(1)O(1) avgO(n)O(n)Worst case needs adversarial hash collisions
Binary heap (heapqheapq)O(1)O(1) (min only)O(n)O(n)O(logn)O(\log n)O(logn)O(\log n) (min)O(n)O(n)heapifyheapify builds from scratch in O(n)O(n)
Binary Search Tree (unbalanced)O(n)O(n) worstO(n)O(n) worstO(n)O(n) worstO(n)O(n) worstO(n)O(n)Degenerates to a linked list on sorted input
Balanced BST (AVL / Red-Black)O(logn)O(\log n)O(logn)O(\log n)O(logn)O(\log n)O(logn)O(\log n)O(n)O(n)Guarantees hold even on adversarial input
TrieO(L)O(L)O(L)O(L)O(L)O(L)O(L)O(L)O(total chars)O(\text{total chars})LL = length of the key/word
Segment TreeO(logn)O(\log n) query/updateO(logn)O(\log n) updateO(n)O(n)O(n)O(n) build; lazy propagation keeps range updates O(logn)O(\log n)
Fenwick Tree (BIT)O(logn)O(\log n) queryO(logn)O(\log n) updateO(n)O(n)Simpler and faster in practice than a segment tree for prefix sums
Disjoint Set Union (DSU)O(α(n))O(\alpha(n)) findfindO(α(n))O(\alpha(n)) unionunionO(n)O(n)With path compression + union by rank/size; α\alpha is the inverse Ackermann function — effectively constant

Graph algorithms

AlgorithmTimeSpaceAnswers
BFSO(V+E)O(V + E)O(V)O(V)Shortest path in an unweighted graph, level order, multi-source distance
DFSO(V+E)O(V + E)O(V)O(V)Reachability, cycle detection, topological order, connected components
Topological sort (Kahn’s or DFS-based)O(V+E)O(V + E)O(V)O(V)A valid linear order of a DAG
Dijkstra (binary heap)O((V+E)logV)O((V + E) \log V)O(V)O(V)Shortest path, non-negative weights only
Bellman-FordO(VE)O(V \cdot E)O(V)O(V)Shortest path with negative weights; also detects negative cycles
Floyd-WarshallO(V3)O(V^3)O(V2)O(V^2)All-pairs shortest paths, works with negative weights (no negative cycles)
Prim’s MST (binary heap)O(ElogV)O(E \log V)O(V)O(V)Minimum spanning tree, dense-ish graphs
Kruskal’s MST (with DSU)O(ElogE)O(E \log E)O(V)O(V)Minimum spanning tree, sparse graphs / edge lists

The Big-O growth hierarchy

From fastest-growing-slowly to fastest-growing-explosively:

O(1)<O(logn)<O(n)<O(n)<O(nlogn)<O(n2)<O(n3)<O(2n)<O(n!)O(1) < O(\log n) < O(\sqrt{n}) < O(n) < O(n \log n) < O(n^2) < O(n^3) < O(2^n) < O(n!)
ComplexityNameFeels like
O(1)O(1)ConstantA dict lookup, an array index
O(logn)O(\log n)LogarithmicBinary search, balanced BST operations
O(n)O(\sqrt{n})RootTrial-division primality check, sparse tables’ build-adjacent factor
O(n)O(n)LinearA single pass — linear search, array sum
O(nlogn)O(n \log n)LinearithmicComparison-based sorting, most divide-and-conquer
O(n2)O(n^2)QuadraticNested loops over the same input — naive pair comparisons
O(n3)O(n^3)CubicTriple-nested loops — Floyd-Warshall, naive matrix multiply
O(2n)O(2^n)ExponentialEnumerating every subset, unmemoized exponential recursion
O(n!)O(n!)FactorialEnumerating every permutation

Max nn per time limit — a rule of thumb

Contest judges typically allow roughly 10810^8-10910^9 simple operations per second in Python (often with a looser limit than C++ for exactly this reason). Working backward from a 1-2 second limit gives a rough ceiling on nn for each complexity class:

ComplexityComfortable max nn
O(n!)O(n!)n10n \le 10-ish
O(2n)O(2^n)n20n \le 20-25
O(n3)O(n^3)n300n \le 300-500
O(n2)O(n^2)n5,000n \le 5{,}000-10,000
O(nlogn)O(n \log n)n106n \le 10^6-10710^7
O(n)O(n)n107n \le 10^7-10810^8
O(logn)O(\log n) / O(1)O(1)nn up to 101810^{18} (the input size barely matters)

Practice

Self-check — reading a constraint. Given n2000n \le 2000 and a 1-second limit, complete the comparison that decides whether an O(n2)O(n^2) solution fits comfortably.

How to use this cheatsheet

  • Treat it as a lookup, not reading material — bookmark it and come back mid-problem when you need to double-check a complexity before committing to an approach.
  • When a problem gives you nn and a time limit, use the “max nn per time limit” table to shortlist which complexity classes are even in the running before you design an algorithm.
  • When two data structures both “work,” pick the one whose row here has the better guarantee for the operation your problem calls the most (e.g. a Fenwick tree over a segment tree when you only need prefix sums, not range updates).

Recap

  • Python’s built-in containers hide real complexity costs — list.pop(0)list.pop(0) and list.insert(0, x)list.insert(0, x) are O(n)O(n), not O(1)O(1); use dequedeque when you need fast operations at both ends.
  • Timsort’s O(nlogn)O(n \log n) worst case and O(n)O(n) best case (on already-sorted runs) is why sorted()sorted()/.sort().sort() is almost always the right call over a hand-rolled sort.
  • Balanced BSTs, Fenwick trees, and segment trees all trade a bit of extra bookkeeping for a guaranteed O(logn)O(\log n) where a naive structure would degrade to O(n)O(n) on adversarial input.
  • Shortest-path algorithm choice is driven entirely by what the graph allows: unweighted -> BFS, non-negative weights -> Dijkstra, negative weights -> Bellman-Ford, all-pairs on a small graph -> Floyd-Warshall.
  • The Big-O hierarchy plus a rough “operations per second” budget is enough to sanity-check almost any algorithm choice before you write a line of code.

Next: Phase 10 — Interview & Contest Strategy, where these templates and complexity guarantees get applied under real time pressure: how to plan an approach, communicate it, and choose the right tool fast when the clock is running.

If this helped you, consider buying me a coffee ☕

Buy me a coffee

Was this page helpful?

Let us know how we did