Big O notation describes how an algorithm’s cost grows with its input, discarding machine-specific constants so competing approaches can be compared by growth rather than one benchmark on one machine.

Formally, f(n) = O(g(n)) means f grows no faster than g beyond some input size. Constants c and n₀ exist such that f(n) ≤ c · g(n) for all n ≥ n₀. Constant factors disappear (3n + 100 is O(n)), as do lower-order terms (n² + n is O(n²)), because the fastest-growing term dominates as n → ∞. This makes scaling comparable across machines. Big O alone still cannot rank algorithms within the same class or distinguish a tight bound from a loose upper bound, and at n = 20 the discarded constants may dominate.

The same notation measures time and space. Time complexity counts operations as a function of input size. Space complexity counts extra memory, including recursion frames that can overflow the call stack.

Core idea: cost as a function of n, keep only the dominant term, drop constants → a hardware-independent growth class that predicts behaviour at scale but not at small n.

The growth classes, side by side

The complexity class is the shape of the curve. Every line begins at the visual origin, then the logarithmic vertical scale covers 1 to 10k operations over the bounded n = 2…10 domain. Factorial is evaluated only through 10!. Once a curve exceeds 10k it leaves the plot, which makes the exponential and factorial jumps visible instead of compressing every practical class against the baseline.

Complexity catalogue

ComplexityGrowthTypical example
O(1)Same time regardless of input sizeArray indexing or a well-distributed hash lookup
O(log log n)Shrinks faster than a fixed-factor split under a strong distribution assumptionInterpolation search over uniformly distributed sorted keys
O(log n)Halves the remaining problem each stepBinary search
O(n)Processes each element onceLinear scan
O(n log n)Performs linear work across logarithmic levelsMerge sort or expected randomized quicksort
O(n²)Repeats linear work for each elementBubble sort or brute-force pair checking
O(2ⁿ)Doubles the state space with each new elementExhaustive subset search
O(n!)Visits every permutationExhaustive permutation search

At n = 10, is 100, 2ⁿ is 1,024, and n! is 3,628,800. Polynomial versus exponential growth is the conventional theoretical tractability boundary, not a guarantee of practical feasibility: an O(2ⁿ) brute-force search usually becomes impractical in the dozens and an O(n!) permutation search in the low teens. The exact cutoff depends on the hardware, latency budget, and work done per state.

The chart’s crossover understates the gap at real input sizes. Counting operations at a few scales makes it concrete:

nlog₂ nnn log₂ n2ⁿ
10~310~33100~1,000
100~7100~66410,000~1.3 × 10³⁰
1,000~101,000~10⁴10⁶~10³⁰¹
1,000,000~2010⁶~2 × 10⁷10¹²beyond astronomical

At a million elements, log₂ n is still about 20 while is a trillion. No constant-factor tuning rescues an algorithm at that scale. Changing it to n log n cuts the operation count by roughly 50,000 times. The 2ⁿ column passes 10³⁰ at n = 100, so an exponential bound is usually a signal to look for dynamic programming, a greedy rule, or an approximation instead of a larger machine.

Common DSA pattern complexities

The table uses n for processed elements, k for a retained subset or window size, m for the number of disjoint-set operations, R for the number of candidate answer values, and C for the cost of one feasibility check. α(n) is the inverse Ackermann function, which grows so slowly that it is effectively constant at practical sizes.

PatternTimeAuxiliary spaceCondition that makes the bound true
Sliding WindowO(n)O(1) to O(k)Each element enters and leaves the window at most once. Stored window state determines the space bound.
Two PointersO(n) after any preprocessingO(1)Coordinated pointers move monotonically. Sorting first adds O(n log n) time and may add space.
Prefix SumO(n) build, O(1) range queryO(n)Static range sums. Updates invalidate the simple prefix array and require a different structure.
Top-K ElementsO(n log k)O(k)A size-k heap keeps only the current winners. Returning sorted output adds O(k log k).
Two HeapsO(log n) insert, O(1) medianO(n)Both halves are retained and rebalanced. Arbitrary deletion needs indexed heaps or lazy deletion.
Binary Search on AnswerO(C log R)Predicate-dependentCandidate answers are discrete and the feasibility predicate is monotonic.
Union-FindO(n + m α(n)) for initialization plus m operationsO(n)Path compression and union by rank/size are both enabled. The bound is amortized over the sequence.
Monotonic Stack or QueueO(n)O(n) worst caseEach element is pushed once and popped at most once, so individual pops amortize across the scan.

Common data-structure operations

V and E denote graph vertices and edges, deg(v) is the degree of vertex v, and L is key length for a trie. Average hash-table bounds assume a suitable hash function and controlled load factor. Amortized bounds describe a sequence of operations, not the worst cost of one call.

StructureAccess / lookupInsertRemoveExplanation
Static arrayIndex O(1). Value search O(n)At position O(n)At position O(n)Contiguous indexing is constant. Interior edits shift a suffix.
Dynamic arrayIndex O(1). Value search O(n)Append O(1) amortized, O(n) worst. Interior O(n)Last O(1). Interior O(n)Occasional capacity growth copies all elements, producing the append worst case.
Linked listO(n) by index or valueO(1) around a held node. Otherwise O(n) to locateO(1) with the required node/predecessor. Otherwise O(n)Pointer rewiring is constant only after the edit location is already known.
Stack / queuePeek O(1)Push/enqueue O(1) amortizedPop/dequeue O(1) amortizedArray-backed forms have an occasional O(n) resize. Linked forms allocate per node.
Hash table or setO(1) average, O(n) worstO(1) average/amortized, O(n) worstO(1) average, O(n) worstCollisions and resize behavior create the worst cases. Hashing the key itself may cost O(L).
Balanced binary search treeO(log n)O(log n)O(log n)Rebalancing keeps tree height logarithmic. An unbalanced BST can degrade to O(n).
Binary heapRoot O(1). Arbitrary search O(n)O(log n)Root O(log n)Heap order constrains parents, not a full search order. Bottom-up build is O(n).
TrieO(L)O(L)O(L)Cost follows key length rather than key count. Memory follows the total stored prefixes and branching representation.
Union-FindFind O(α(n)) amortizedMake-set O(1). Union update/merge O(α(n)) amortizedUnsupportedThe inverse-Ackermann bound requires path compression and union by rank/size. A standard disjoint-set forest cannot split a set by removing an element.
Graph adjacency listEdge lookup O(deg(u)). Neighbor scan O(deg(u))Edge O(1) amortizedEdge O(deg(u))Space is O(V + E). Using a set per neighbor list changes expected edge lookup/update toward O(1) at extra overhead.
Graph adjacency matrixEdge lookup O(1). Neighbor scan O(V)Edge O(1)Edge O(1)Space is O(V²), which pays for constant-time edge membership.

Space complexity and the cases

Space includes the call stack. A recursive traversal that allocates O(1) heap memory may still use O(h) stack frames, where h is the recursion depth. A chain-shaped input 100k nodes deep can overflow a thread stack without allocating heap objects. Auxiliary space means memory beyond the input: merge sort uses O(n) for its merge buffer, naive recursive quicksort uses O(log n) expected and O(n) worst-case stack space, while an in-place scan uses O(1).

A single algorithm has different bounds depending on the input, and the distinction is not pedantic:

  • Worst case — the guarantee under adversarial or degenerate input. What an SLA or a security boundary is written against. A hash-map lookup is O(n) worst case when every key collides.
  • Average case — expected cost over a distribution of inputs. A hash-map lookup is O(1) average, a bound commonly used for capacity planning.
  • Best case — the floor. Usually uninteresting except to note it (a target found on the first probe is O(1)).
  • Amortised — cost averaged over a sequence of operations, distinct from average case. Dynamic-array append is O(1) amortised even though a single resize is O(n), and union-find is O(α(n)) amortised per operation — a guarantee over the whole sequence, not any one call.

Big O states an upper bound. Big Θ (theta) states a tight bound where the upper and lower bounds match: merge sort is Θ(n log n) in every case, while quicksort is O(n²) worst case and Θ(n log n) on average. Big Ω (omega) states a lower bound. Informal discussion often uses “O” for a tight bound, but Θ is the precise notation for matching asymptotic upper and lower bounds.

Where Big O misleads

  • Constants matter at small n. Big O drops them, so an O(n log n) algorithm with heavy setup can lose to an O(n²) one on small inputs. Array.Sort switches to insertion sort for small subarrays inside its O(n log n) introsort because the quadratic algorithm has a smaller constant on short partitions.
  • The hidden constant can be huge. Two O(n) algorithms can differ 100× in wall-clock from cache behaviour, branch prediction, or allocation. Big O narrows the field. Profiling on representative data picks the winner within a class.
  • n” must be defined. For string work, is n the number of strings or their total length? A trie lookup is O(L) in key length, independent of the n keys stored — stating the bound without naming the variable is meaningless.
  • The base of a logarithm is irrelevant. log₂ n and log₁₀ n differ by a constant factor, which Big O drops, so O(log n) needs no base. Inside an exponent the base is decisive: 2ⁿ and 3ⁿ are different classes.

Questions

References