Skip to main content

Updated Sep 11, 2026

Common Complexities

In practice you meet perhaps eight growth classes. Recognising which one a piece of code falls into — and, more usefully, recognising the problem shape that produces each — is most of what complexity analysis is for day to day.

The classes below are ordered by growth rate, each with one named, real algorithm rather than an invented example, because the named algorithm is what makes the class memorable and gives it a case to reason about (see Big-O Notation for what "case" and "O" formally mean).

Core Concepts​

O(1)O(1) — Constant​

Direct addressing: reading a[i] from an array, or a hash table lookup, cost the same regardless of how large the collection is. Array indexing is O(1)O(1) worst case, arithmetic on a known offset; hash lookup is O(1)O(1) average case and O(n)O(n) worst case, since a pathological set of keys can collide into one bucket.

O(log⁡n)O(\log n) — Logarithmic​

Binary search over sorted data: each comparison discards half of what remains, so the number of comparisons is worst-case O(log⁡n)O(\log n). Balanced binary search tree operations (insert, find, delete) share the same bound because the tree's height is kept O(log⁡n)O(\log n).

O(n)O(n) — Linear​

A single pass that looks at each element a fixed number of times: summing an array, finding its maximum, or linear search through unsorted data — O(n)O(n) worst case, since nothing rules out the target being last or absent.

O(nlog⁡n)O(n \log n) — Linearithmic​

Divide-and-conquer with linear work to combine the halves: mergesort and heapsort are both Θ(nlog⁡n)Θ(n \log n) worst case. So is any comparison-based sort, for a reason worth stating precisely — see below.

O(n2)O(n^2) — Quadratic​

Every pair: bubble sort and insertion sort compare or shift adjacent pairs in the worst case, and a naive duplicate check compares every element against every other. O(n2)O(n^2) worst case for all three.

O(n3)O(n^3) — Cubic​

Every triple: the textbook triple-nested matrix multiplication and the Floyd–Warshall all-pairs shortest-path algorithm both do O(n3)O(n^3) worst-case work, one multiply-add or relaxation per triple of indices.

O(2n)O(2^n) — Exponential​

Every subset: the naive recursive solution to subset-sum tries all 2n2^n subsets, and unmemoized recursive Fibonacci recomputes the same subtree exponentially many times — both O(2n)O(2^n) worst case.

O(n!)O(n!) — Factorial​

Every ordering: brute-force travelling salesman and plain permutation generation both enumerate all n!n! orderings of the input, worst case, best case and average case alike — there is no shortcut input.

The shape usually tells you the class

"For each element, do a fixed thing" → linear. "For each pair" → quadratic. "Halve it each time" → logarithmic. "Try every subset" → exponential. "Try every ordering" → factorial. Reading the problem statement often gives the exponent before any code is written.

Mechanism​

The linearithmic barrier​

O(nlog⁡n)O(n \log n) shows up constantly, and not by accident: comparison-based sorting cannot do better. Any algorithm that only compares elements must distinguish between all n! possible orderings, and a binary comparison yields one bit, so it needs at least log⁡2(n!)≈nlog⁡2n−1.44n\log_2(n!) \approx n \log_2 n - 1.44n comparisons worst case (Cormen, Leiserson, Rivest & Stein, Introduction to Algorithms, 4th ed., Ch. 8, "Sorting in Linear Time" — the decision-tree argument).

That is a proof about a model, not about sorting itself. Algorithms that inspect the values rather than only comparing them — counting sort, radix sort, bucket sort — escape it and reach O(n)O(n) worst case, at the price of assuming something about the keys (bounded range, fixed width).

Wall-clock cost at n = 10⁶​

At roughly one billion simple operations per second, fixing n = 10⁶ across the classes above:

ClassOperations at n = 10⁶Wall-clock time
O(1)O(1)1negligible
O(log⁡n)O(\log n)~20negligible
O(n)O(n)1,000,000~1 ms
O(nlog⁡n)O(n \log n)~20,000,000~20 ms
O(n2)O(n^{2})10¹²~17 minutes
O(n3)O(n^{3})10¹⁸~32 years
O(2n)O(2^{n})2¹,⁰⁰⁰,⁰⁰⁰unreachable — more operations than atoms in the observable universe

The O(nlog⁡n)O(n \log n) row worked out, so the rest of the table can be redone by hand:

n = 1,000,000
log₂(1,000,000) ≈ 19.93 → round to 20

operations ≈ n · log₂ n
≈ 1,000,000 × 20
= 20,000,000

time ≈ operations / (10⁹ operations per second)
≈ 20,000,000 / 1,000,000,000
≈ 0.02 s = 20 ms

The same recipe gives every other row: for O(n2)O(n^{2}), operations = n² = 10¹², divided by 10⁹ gives 1,000 seconds, which is where the ~17-minute figure comes from.

import math


def estimate_seconds(n, class_name, ops_per_sec=1_000_000_000):
"""Reproduce one row of the table above from its growth class."""
if class_name == "n":
ops = n
elif class_name == "n log n":
ops = n * math.log2(n)
elif class_name == "n^2":
ops = n**2
else:
raise ValueError(class_name)
return ops / ops_per_sec


assert round(estimate_seconds(1_000_000, "n log n"), 2) == 0.02 # matches the worked row above
assert estimate_seconds(1_000_000, "n") < estimate_seconds(1_000_000, "n^2")

Practical Usage​

Rough guidance on what is tractable, assuming ~10810^8–10910^9 simple operations per second, worst case unless noted:

Input sizeWhat is comfortably affordable
n ≤ 10Anything, including O(n!)O(n!)
n ≤ 25O(2n)O(2^n)
n ≤ 500O(n3)O(n^3)
n ≤ 10,000O(n2)O(n^2)
n ≤ 10,000,000O(nlog⁡n)O(n \log n)
n > 10,000,000O(n)O(n) or O(log⁡n)O(\log n) — and start caring about memory bandwidth

Two growth classes get their own page, because there is more to say about each than fits a single table row: Amortized Analysis covers operations that are usually O(1)O(1) and occasionally O(n)O(n), such as a dynamic array's append; Space Complexity covers the same asymptotic language applied to memory rather than time, including the stack cost recursion hides.

Edge Cases & Pitfalls​

  • O(1)O(1) is not a promise of speed. A hash lookup that computes a cryptographic digest is O(1)O(1) average case and slower in practice than scanning a ten-element array.
  • The constant can dominate at realistic sizes. Strassen's matrix multiplication is asymptotically better than the naive O(n3)O(n^3) worst case and loses on small matrices; galactic algorithms take this to its absurd conclusion, beating everything asymptotically at input sizes exceeding the number of atoms in the universe.
  • Memory access is not O(1)O(1) on real hardware. The model assumes uniform-cost memory. Actual machines have a cache hierarchy spanning two orders of magnitude in latency, which is why a "worse" algorithm with sequential access often wins at n where the table above says it should lose.
  • The named algorithm is not the only route to its class. Several unrelated algorithms share a growth class for different reasons — mergesort and heapsort are both Θ(nlog⁡n)Θ(n \log n) worst case, but their mechanisms (recursive merge versus heap extraction) share nothing. The class predicts cost, not implementation.

Comparisons​

ClassBeats O(n2)O(n^{2}) whenLoses to O(n2)O(n^{2}) when
O(nlog⁡n)O(n \log n)n is large enough that the log factor is cheap next to a full quadratic sweepn is tiny and the constant behind O(nlog⁡n)O(n \log n) (recursion, merging) outweighs a simple double loop
O(n)O(n) (counting/radix sort)The keys are bounded-range integers, so comparisons can be skipped entirelyThe key range k is itself large — cost is O(n+k)O(n + k), and a huge k defeats the point
O(2n)O(2^{n}) (exact, exponential)n is small enough that exactness matters more than speed (n ≤ ~25)n grows past a few dozen — an O(n2)O(n^{2}) approximation usually beats an exact exponential algorithm that never finishes

Recall​

References​

  • Cormen, Leiserson, Rivest & Stein, Introduction to Algorithms, 4th ed., Ch. 8 — "Sorting in Linear Time", the comparison-sort decision-tree lower bound and the counting/radix/bucket sorts that escape it.
  • Sedgewick & Wayne, Algorithms, 4th ed., §1.4 — "Analysis of Algorithms", with empirical measurement (the doubling ratio test) alongside the theory.
  • Big-O Notation — what the notation formally asserts, and the case each bound above is stated in.
  • Amortized Analysis — bounds that are cheap on average over a sequence of operations, not on every single call.
  • Space Complexity — the same growth classes applied to memory.
  • Choosing a Sort — these trade-offs applied to one concrete decision.