Big-O Cheat Sheet
Look up the time and space complexity of every common data structure and sorting algorithm — best, average and worst case, entirely on this device.
All 34 rows shown
Data Structures
Access, search, insertion and deletion show the average and worst case; space is the worst-case auxiliary memory, which excludes the input.
| Name | Accessavg / worst | Searchavg / worst | Insertionavg / worst | Deletionavg / worst | Space | Notes |
|---|---|---|---|---|---|---|
| ArrayLinear | O(1) / O(1) | O(n) / O(n) | O(n) / O(n) | O(n) / O(n) | O(n) | Access by index is O(1). Insertion and deletion are O(n) because they are measured at an arbitrary position and shift the trailing elements; appending to the end of a dynamic array is amortized O(1). Search is O(1) only if you already know the index. Scanning for a value in an unsorted array is O(n); binary search over a sorted array is O(log n) — see the tree rows for structures that keep data ordered for you. |
| StackLinear | O(n) / O(n) | O(n) / O(n) | O(1) / O(1) | O(1) / O(1) | O(n) | Push and pop act only on the top, so insertion and deletion are O(1). Reaching an element beneath the top (access or search) means popping through the ones above it, which is O(n). |
| QueueLinear | O(n) / O(n) | O(n) / O(n) | O(1) / O(1) | O(1) / O(1) | O(n) | Enqueue and dequeue touch only the two ends, so both are O(1); reaching an interior element is O(n). The complexity profile matches a stack — only the access order (FIFO vs LIFO) differs. |
| Singly-Linked ListLinear | O(n) / O(n) | O(n) / O(n) | O(1) / O(1) | O(1) / O(1) | O(n) | The O(1) insertion and deletion assume you already hold the node (or the head/tail). Locating that node first is a linear walk, so an insert-after-value operation is O(n) in total. There is no random access — reaching index i is O(n). |
| Doubly-Linked ListLinear | O(n) / O(n) | O(n) / O(n) | O(1) / O(1) | O(1) / O(1) | O(n) | Like a singly-linked list, splicing is O(1) once you hold the node. The extra backward pointer lets you delete a known node without first scanning for its predecessor, which a singly-linked list needs; finding the node in the first place is still O(n). |
| Skip ListProbabilisticexpected | O(log n) / O(n) | O(log n) / O(n) | O(log n) / O(n) | O(log n) / O(n) | O(n log n) | The O(log n) figures are expected values over the random level assignment, not a guarantee — an unlucky build degrades to the O(n) worst case, so these cells are probabilistic rather than deterministic. Space is O(n log n) because higher express lanes duplicate node pointers. |
| Hash TableHashingamortized | — / — | O(1) / O(n) | O(1) / O(n) | O(1) / O(n) | O(n) | Search, insertion and deletion are O(1) on average and amortized, but the worst case is O(n) when many keys collide into one bucket or a resize rehashes every entry. Access has no meaning: a hash table has no positional index, so that cell is shown as an em-dash rather than a fabricated O — you look items up by key, not by position. |
| Binary Search TreeTree | O(log n) / O(n) | O(log n) / O(n) | O(log n) / O(n) | O(log n) / O(n) | O(n) | O(log n) assumes the tree stays roughly balanced. A plain BST fed already-sorted keys degenerates into a linked list, so its worst case is O(n). The self-balancing rows below (AVL, red-black, B-tree) buy back the O(log n) guarantee. |
| Cartesian TreeTree | — / — | O(log n) / O(n) | O(log n) / O(n) | O(log n) / O(n) | O(n) | A binary tree derived from a sequence; access by index is not applicable, so that cell is an em-dash. Search, insertion and deletion average O(log n) but degrade to O(n) without balancing (a randomized treap keeps them expected O(log n)). |
| B-TreeTree | O(log n) / O(log n) | O(log n) / O(log n) | O(log n) / O(log n) | O(log n) / O(log n) | O(n) | A self-balancing multi-way tree with a deterministic O(log n) worst case for every operation. Its wide, shallow shape minimizes disk seeks, which is why databases and filesystems use B-trees and their B+ variant for on-disk indexes. |
| Red-Black TreeTree | O(log n) / O(log n) | O(log n) / O(log n) | O(log n) / O(log n) | O(log n) / O(log n) | O(n) | A self-balancing BST with a deterministic O(log n) worst case. It rebalances with fewer rotations than an AVL tree, trading slightly looser height for cheaper writes; it backs many standard-library ordered maps and sets. |
| Splay TreeTreeamortized | — / — | O(log n) / O(log n) | O(log n) / O(log n) | O(log n) / O(log n) | O(n) | The O(log n) figures are amortized, not per-operation worst case: a single splay can cost O(n), but any sequence of m operations totals O(m log n). Splaying moves recently used keys near the root, so repeated access to a hot set is very fast. |
| AVL TreeTree | O(log n) / O(log n) | O(log n) / O(log n) | O(log n) / O(log n) | O(log n) / O(log n) | O(n) | The most rigidly balanced tree here, with a deterministic O(log n) worst case. Its tighter height makes lookups a touch faster than a red-black tree, at the cost of more rotations on insert and delete. |
| KD TreeTree | O(log n) / O(n) | O(log n) / O(n) | O(log n) / O(n) | O(log n) / O(n) | O(n) | A binary tree that partitions k-dimensional points for range and nearest-neighbour queries. O(log n) is the balanced average; an unbalanced build degrades to O(n), and the advantage erodes as the number of dimensions grows. |
Sorting Algorithms
Best, average and worst are time complexity; space is the worst-case auxiliary memory, which excludes the input.
| Name | Best | Average | Worst | Space | Stable | Notes |
|---|---|---|---|---|---|---|
| QuicksortPartitioning | O(n log n) | O(n log n) | O(n²) | O(log n) | No | The O(n²) worst case comes from consistently bad pivots — for example already-sorted input with a naive first-element pivot. Randomized or median-of-three pivots make it very unlikely in practice. Space O(log n) is the recursion stack for the in-place partition (auxiliary space excludes the input array). Without tail-call elimination, an unbalanced recursion can push that to O(n). |
| Merge SortMerging | O(n log n) | O(n log n) | O(n log n) | O(n) | Yes | Deterministic O(n log n) in every case, and stable. The O(n) space is the auxiliary buffer the merge step needs — array merge sort is not in-place. Space here is auxiliary, excluding the input; this convention holds for every row. |
| TimsortInsertion & merging | O(n) | O(n log n) | O(n log n) | O(n) | Yes | A hybrid of merge sort and insertion sort that detects existing sorted runs and galloping patterns, giving an O(n) best case on nearly-sorted input. It is the default sort in Python's list.sort and Java's Arrays.sort for objects, and it is stable. |
| HeapsortSelection | O(n log n) | O(n log n) | O(n log n) | O(1) | No | Deterministic O(n log n) and in-place, so its auxiliary space is O(1). The trade-offs are that it is not stable and its scattered heap accesses have poorer cache locality than quicksort's sequential partitioning. |
| Bubble SortExchanging | O(n) | O(n²) | O(n²) | O(1) | Yes | The O(n) best case needs the early-exit optimization: stop after a full pass that makes no swaps, which happens on already-sorted input. Without that check, best is also O(n²). Mainly of teaching interest. |
| Insertion SortInsertion | O(n) | O(n²) | O(n²) | O(1) | Yes | O(n) best case on already-sorted input, and stable and in-place. It is genuinely fast for small or nearly-sorted arrays, which is why optimized quicksort and merge sort fall back to it below a size threshold. |
| Selection SortSelection | O(n²) | O(n²) | O(n²) | O(1) | No | There is no early exit: every pass scans the whole unsorted remainder to find the minimum, so best, average and worst are all O(n²) regardless of the input order. It does, however, make the fewest writes of the quadratic sorts. |
| Tree SortInsertion | O(n log n) | O(n log n) | O(n²) | O(n) | Yes | Insert every element into a binary search tree, then read it back in order. It is O(n log n) when the tree stays balanced and O(n²) when it degenerates — the same hazard as an unbalanced BST; a self-balancing tree removes the quadratic case. |
| ShellsortInsertion | O(n log n) | O(n(log n)²) | O(n(log n)²) | O(1) | No | Insertion sort applied over diminishing gaps so elements move a long way early. The bounds shown assume a common gap sequence; the exact average and worst complexity of Shellsort depends on the gap sequence chosen and is still an open problem for some sequences. |
| Bucket SortDistribution | O(n+k) | O(n+k) | O(n²) | O(n) | Yes | A distribution sort: scatter n items into k buckets, sort each, then concatenate. It is O(n + k) when inputs spread evenly across buckets, but skewed data that piles into one bucket collapses toward the O(n²) worst case. |
| Radix SortDistribution | O(nk) | O(nk) | O(nk) | O(n+k) | Yes | A non-comparison sort that processes keys digit by digit, where k is the key length (number of digits or bytes). It beats O(n log n) only when k is small relative to n; for long or unbounded keys the k factor dominates. |
| Counting SortDistribution | O(n+k) | O(n+k) | O(n+k) | O(k) | Yes | A non-comparison sort that tallies occurrences over a value range of size k, so it runs in O(n + k). The O(k) space makes it impractical when the range is large or sparse; it shines on small integer keys and often serves as the stable inner pass of radix sort. |
| CubesortInsertion | O(n) | O(n log n) | O(n log n) | O(n) | Yes | A parallel sort that inserts elements into a self-balancing multidimensional array (a cube) and reads them back in order. It reaches an O(n) best case on already-ordered input and O(n log n) otherwise, and it is stable. |
Complexity Classes
| Class | Big-O | Meaning | Example |
|---|---|---|---|
| Constant | O(1) | Runtime does not grow with the input size. | Reading an array element by index; an average hash-table lookup. |
| Logarithmic | O(log n) | Work rises by a fixed amount every time the input doubles. | Binary search; a lookup in a balanced search tree. |
| Linear | O(n) | Runtime grows in direct proportion to the input size. | A single pass over an array to sum or scan it. |
| Linearithmic | O(n log n) | Grows like n multiplied by log n — the bound of the fastest comparison sorts. | Merge sort and heapsort in every case. |
| Quadratic | O(n²) | Runtime grows with the square of the input size. | Comparing every pair of items; a naive nested-loop sort. |
| Exponential | O(2ⁿ) | Runtime doubles with each additional input element. | Enumerating every subset of a set by brute force. |
| Factorial | O(n!) | Runtime grows with the factorial of the input size. | Generating every permutation; brute-force travelling salesman. |
- ConstantO(1)
- LogarithmicO(log n)
- LinearO(n)
- LinearithmicO(n log n)
- QuadraticO(n²)
- ExponentialO(2ⁿ)
- FactorialO(n!)
Each curve shows how one complexity class grows with the input size n. The curves share a clamped axis, so compare their shapes rather than their exact heights.
Heights are relative and clamped for large n — read the shape, not the value.
No row matches that filter
The filter matches names, aliases and Big-O values across the tables below. Try a shorter word like tree or hash, a notation like O(n), or press Show all rows.
Do it offline:$ cppman std::sort
How to use it
- Scan the tables: data structures first, then sorting algorithms, then the complexity classes with their growth chart. Every cell is the value the engine renders, so the tables and the worked examples below stay in step.
- Filter to what you need. Type a name like heapsort, a family like tree, or a complexity token like O(n²); the rows collapse to the matches and the count line updates. Press / to jump to the box.
- Read across a row. For a structure that is access, search, insertion and deletion — each shown as average then worst — and the auxiliary space; for a sort it is best, average, worst, space and whether it is stable.
- Check the row's notes and its #slug anchor. The notes say when a figure assumes balancing, a lucky pivot or a bounded key range, and the row name links to that anchor — a deep link you can share.
Look up the cost of every common data structure and sorting algorithm: best, average and worst case for each operation, plus the auxiliary space it needs. The tables are server-rendered HTML that answer before any JavaScript runs, and you can filter by a name like quicksort or a complexity such as O(n log n). Nothing you type leaves this device.
How it works
Big-O describes how an algorithm's cost grows as the input size n grows, dropping constant factors and lower-order terms. A routine that runs 3n + 20 steps and one that runs n/2 steps are both O(n): only the dominant term survives, because for large n it swamps the rest. So a row lists a class like O(n log n), not a step count — the shape of the growth, not a benchmark.
Strictly, O is an upper bound, Ω (omega) a lower bound, and Θ (theta) a tight bound that is both at once. Cheat sheets, this one included, write O everywhere out of habit even when the bound is tight, so the best and average columns are really Θ or Ω wearing an O. The log base is dropped like the constants: a change of base is only a constant factor, so every logarithm is one class, O(log n).
The three time columns are different promises. Worst case is the ceiling that holds for any input, so it is the one to size a system against; average case assumes typical or random input. Amortized is different again — it averages one operation over a long run, which is how a dynamic array calls append O(1) though the odd call copies the whole buffer. Cells that are amortized or expected carry a badge, so you never mistake them for a hard worst case.
The space column is auxiliary space — the extra memory beyond the input. That is why an in-place sort like heapsort is O(1): the input array is not recounted. Recursion counts too, so quicksort's O(log n) space is its call-stack depth, not a second copy of the data.
Examples
QuicksortQuicksort — best O(n log n) · average O(n log n) · worst O(n²) · space O(log n)Fast on average but not guaranteed: bad pivots push the worst case to O(n²), and the O(log n) space is the in-place recursion stack.
Hash TableHash Table — access —/— · search O(1)/O(n) · insertion O(1)/O(n) · deletion O(1)/O(n) · space O(n)Access is an em-dash, not a fake O — there is no positional index. The O(1) is average and amortized; a collision pile-up or rehash is the O(n) worst case.
Binary Search TreeBinary Search Tree — access O(log n)/O(n) · search O(log n)/O(n) · insertion O(log n)/O(n) · deletion O(log n)/O(n) · space O(n)O(log n) holds only while the tree stays balanced. Sorted insertions degenerate it into a linked list, which is the O(n) worst case the self-balancing trees below remove.
Counting SortCounting Sort — best O(n+k) · average O(n+k) · worst O(n+k) · space O(k)A non-comparison sort that beats O(n log n), but only while the value range k stays small — kept explicit in the time and the O(k) space.
TimsortTimsort — best O(n) · average O(n log n) · worst O(n log n) · space O(n)The real-world default in Python and Java: it detects existing sorted runs for an O(n) best case, stays O(n log n) otherwise, and is stable.
Every example on this page is executed against the same engine that powers the tool, on every build.
Gotchas worth knowing
Array append is amortized O(1), not O(n)
The insertion and deletion cells read O(n) because they are measured at an arbitrary position and shift the elements after it. Appending to a dynamic array's end is amortized O(1) — the row's note labels the split.
A hash table has no Access cell
Access shows an em-dash, not a fabricated O, because you reach a hash table by key, not by position. Its O(1) search, insert and delete are average and amortized; a collision run or a resize rehash makes the worst case O(n).
Linked-list O(1) assumes you hold the node
Splicing a node in or out is O(1) only once you hold a reference to it. Locating that node is a linear walk, so insert-after-value costs O(n) end to end, and reaching index i is O(n) — there is no random access.
A best case usually means an early exit
Bubble, insertion and Timsort reach O(n) on already-sorted input only because they stop early or spot existing runs. Selection sort has no such shortcut — it rescans the whole remainder every pass — so its best, average and worst are all O(n²).
Space is auxiliary, excluding the input
Every space cell counts only memory beyond the input array. Heapsort is O(1) sorting in place, merge sort is O(n) for its merge buffer, and quicksort is O(log n) for a recursion stack that can reach O(n) when unbalanced.
Linear-time sorts need a bounded k
Counting, radix and bucket sort beat the O(n log n) comparison barrier only while the key range or width k stays small next to n. The tables keep k visible — O(n+k), O(nk), O(k) space — so the assumption behind the speed is never hidden.
Common errors
No row matches that filter- The state when a query matches no row. It is never a dead end: the block beneath names the shapes the filter understands — a name, a group, or a complexity token — and Show all rows restores every one of the 34 rows.
Reading an average or amortized cell as a worst-case guarantee- The most common misread of the tables. A hash table's O(1) or a splay tree's O(log n) is what you get on typical input averaged over time, not a promise for every call; a latency budget belongs to the badged worst-case column instead.
Assuming the best-case column always applies- The other frequent slip. A best case is a specific, often optimistic condition — already-sorted input, an ideal pivot, an even hash spread — so it is the wrong column for capacity planning. When unsure, read the worst-case column.
FAQ
Is a smaller Big-O always faster in practice?
Not always. Big-O hides constant factors and cache behaviour, so an O(n log n) sort with a tight loop can beat an O(n) one that thrashes memory, and insertion sort wins on tiny arrays. It predicts how cost scales, not the winner at the size you run.
Why is O(n log n) the best a comparison sort can do?
A comparison sort must tell every possible permutation apart, and there are n! of them; a decision tree separating them needs about n log n comparisons. Counting and radix sort duck under that floor by not comparing keys at all.
When do counting sort and radix sort actually beat quicksort?
Only when the key range or width k is small next to the element count n. Sorting a million bytes with counting sort is a clear win; a million long unique strings let the k factor dominate, and a good O(n log n) sort wins again.
What separates the average and worst-case columns?
Average case is the cost on typical or random input; worst case is the ceiling for any input at all. Balanced trees and merge sort quote one value in both because they are deterministic; quicksort and a plain BST diverge, which is where the risk sits.
Why can a binary search tree degrade to O(n)?
A plain tree keeps whatever shape its insertions give it. Feed it sorted keys and it grows into a straight line — effectively a linked list — so lookups walk every node. AVL, red-black and B-trees rebalance to keep their O(log n) height.
Does this cheat sheet cover graph algorithms?
Not in this version. It lists linear structures, trees, hash tables and the common sorts, plus the seven growth classes. Graph costs are written in vertices and edges, not a single n, so they are planned separately to be labelled honestly.
Is there a command that prints this whole cheat sheet?
No single command does. The line below, cppman std::sort, opens the C++ standard's sort page in a third-party cppreference viewer — one algorithm's O(N log N) guarantee, a single entry, not a table. Treat this page as the reference and the command as a spot check.
Keyboard shortcuts
- /
- Focus the filter