O(n)

A cheat sheet for asymptotic complexity

Every algorithm is
fast enough
until it isn't.

Complexity says nothing about how fast your code runs today. It says what happens to that number when the input grows. Drag n and watch the same nine curves separate.

Ready for a four-minute review?

1,000

Timings assume a billion operations a second — change the assumption in How much input fits?

    Log–log paper: every gridline is ten times the last, on both axes. A power law plots as a straight line and its exponent is the slope — which is why O(n²) climbs at twice the angle of O(n), and why the exponentials leave the page almost immediately. Each curve carries its own dash pattern too, so the nine stay distinct without relying on colour. Tap a curve or a ladder row to pin its class.
    Highlight

    Type to filter the sheet. Enter jumps to the first result; the up and down arrows move through matches.

    Pick a class to reveal every operation with that growth rate — in the cards, the tables, the ladder and the plot.

    Your review

    Five focused questions: due work first, then a recent weak spot and a new transfer question. Miss one and it returns in this session; get it right and it comes back in 3, 7, 21, then 60 days.

    How to read this sheet

    Sixty seconds, three ideas. Everything below is one of them applied to a particular structure.

    1. 1

      What is growing?

      n is the size of the input — elements in the list, keys in the map, vertices in the graph. A complexity is a promise about what happens to the work when n gets bigger, not about how long anything takes today.

    2. 2

      Which case?

      Best, average and worst describe different inputs, not different machines. Average is what you will feel; worst is what wakes you at 3am. Amortised is a third thing: expensive rarely, cheap the rest of the time, averaged over a run of operations.

    3. 3

      Read the shape, not the stopwatch.

      Constants and lower terms are dropped. 3n² + 2n + 7 and n² share a colour here, because past some n the square is all that is left.

    4. One worked example

      Double the array. An O(n) pass does about 2× the work it did before. An O(n²) pass does about 4×. An O(log n) lookup does one extra step. That ratio — not the millisecond count — is what every row on this page is telling you.

      The full notation guide is at the bottom →

    Start from what you need

    The question you actually arrived with, and the structure that answers it — every answer a link into the sheet.

    Linear structures

    Elements in a row. What separates them is whether reaching position i costs arithmetic or a walk.

    Array list

    Contiguous, index arithmetic, doubles when full

    amortisedcache-friendly

    OperationAvgWorst
    Access by index11
    Searchnn
    Append1n
    Insert at indexnn
    Remove at indexnn
    Remove last11
    Iterate in ordernn
    Spacen
    Why?

    Append is amortised: one insertion in every n triggers a resize that copies everything. Contiguity is also why iteration beats a linked list by an order of magnitude in practice — the numbers here do not show cache lines.

    In practice Equal complexity to a linked list for traversal, and usually 5–10× faster: the prefetcher can see where you are going.

    Sorted array

    The same array, kept in order — search collapses to binary

    worst casecache-friendly

    OperationAvgWorst
    Access by index11
    Searchlog nlog n
    Insertnn
    Deletenn
    Min / max11
    Predecessor / successorlog nlog n
    Range scan (k results)log n + klog n + k
    Spacen
    Why?

    Cheap to read, expensive to change. Ideal for data built once and queried often; a poor fit for anything written to at runtime.

    In practice Binary search is O(log n) but jumps around memory; for n under a few hundred a linear scan of a packed array often wins.

    Singly-linked list

    Nodes and forward pointers, with a head and (usually) a tail

    worst case

    OperationAvgWorst
    Access by indexnn
    Searchnn
    Insert at front11
    Insert at back (with tail)11
    Insert after a known node11
    Delete a known nodenn
    Delete from front11
    Spacen
    Why?

    Deleting a node you already hold is linear here, not constant: you need its predecessor, and finding that means walking from the head. This is the one asymmetry a doubly-linked list removes.

    In practice The O(1) insert assumes you already hold the node. Finding it is the O(n) you actually pay.

    Doubly-linked list

    Pointers both ways, at the cost of one extra word per node

    worst case

    OperationAvgWorst
    Access by indexnn
    Searchnn
    Insert at either end11
    Insert beside a known node11
    Delete a known node11
    Delete from either end11
    Iterate in reversenn
    Spacen
    Why?

    Constant-time removal of a node you already hold is what makes it the list inside an LRU cache: the hash table hands you the node, the list splices it out.

    In practice Two pointers per element is real memory. It earns its keep when something else — a hash map, in an LRU cache — hands you the node.

    Stack

    One end only. Last in, first out

    amortisedcache-friendly

    OperationAvgWorst
    Push11
    Pop11
    Peek11
    Searchnn
    Is empty11
    Spacen
    Why?

    Worst case is constant on a linked implementation and amortised on an array one, where a push can trigger a resize.

    Queue

    Ring buffer or linked. First in, first out

    amortisedcache-friendly

    OperationAvgWorst
    Enqueue11
    Dequeue11
    Peek front11
    Searchnn
    Is empty11
    Spacen
    Why?

    In practice On a ring buffer every operation is a couple of index updates; on a linked list it is an allocation.

    Deque

    Both ends cheap; a stack and a queue in one structure

    amortisedcache-friendly

    OperationAvgWorst
    Push / pop front1n
    Push / pop back1n
    Access by index11
    Insert in the middlenn
    Searchnn
    Spacen
    Why?

    Indexed access is constant on the usual block-array implementation (C++ deque; Python's collections.deque is linked and gives up on it). The linear worst case is the push that doubles the ring buffer — amortised away across a run of pushes, and pops never pay it.

    In practice Usually a list of fixed blocks, so indexing costs an extra indirection compared with a plain array.

    Back to the section menu ↑

    Hash tables

    Constant time on average, linear when every key lands in the same place. Which one you get depends on the hash and the load factor.

    Hash table (chaining)

    Buckets of linked entries; collisions extend a chain

    averageexpected

    OperationAvgWorst
    Lookup by key1n
    Insert1n
    Delete1n
    Iterate (unordered)nn
    Iterate in key ordern log nn log n
    Resize / rehashnn
    Min / max keynn
    Spacen
    Why?

    The linear worst case is every key hashing to one bucket. Against untrusted input that is a denial-of-service vector, which is why runtimes seed their hashes randomly at startup.

    In practice Constant *average*. With adversarial keys and no randomised hash, every key can land in one bucket and lookup becomes linear.

    Hash table (open addressing)

    One entry per slot; collisions probe onward to the next free one

    averagecache-friendly

    OperationAvgWorst
    Lookup by key1n
    Insert1n
    Delete1n
    Iterate (unordered)nn
    Resize / rehashnn
    Spacen
    Why?

    Faster than chaining while the table is under about 70% full, and it degrades sharply past that. Deletion needs tombstones, which accumulate until a rehash clears them.

    In practice Faster than chaining while the load factor stays under ~0.7, because a probe sequence stays inside a cache line or two. Past that it degrades sharply.

    Back to the section menu ↑

    Trees and ordered structures

    What you buy with log n instead of 1: the keys stay in order, so ranges, neighbours and min/max are all cheap.

    Binary search tree

    Unbalanced. Fine on random input, a list on sorted input

    average

    OperationAvgWorst
    Searchlog nn
    Insertlog nn
    Deletelog nn
    Min / maxlog nn
    Predecessor / successorlog nn
    In-order traversalnn
    Heightlog nn
    Spacen
    Why?

    Every worst case in this card is the same event: keys arriving in sorted order, which builds one long spine.

    In practice Unbalanced. Insert sorted data and it degenerates into a linked list — which is why nobody ships a plain BST.

    AVL tree

    Height-balanced within one level, by rotation

    worst case

    OperationAvgWorst
    Searchlog nlog n
    Insertlog nlog n
    Deletelog nlog n
    Min / maxlog nlog n
    Predecessor / successorlog nlog n
    In-order traversalnn
    Spacen
    Why?

    Stricter balance than red-black, so lookups are shallower and writes rotate more often. Prefer it for read-heavy workloads.

    In practice Stricter balance than red-black: faster lookups, more rotations on write. Good for read-heavy indexes.

    Red-black tree

    Looser balance, fewer rotations — the usual ordered map

    worst case

    OperationAvgWorst
    Searchlog nlog n
    Insertlog nlog n
    Deletelog nlog n
    Min / maxlog nlog n
    Predecessor / successorlog nlog n
    In-order traversalnn
    Spacen
    Why?

    What std::map, TreeMap and most kernel schedulers are built from: at most two rotations to rebalance an insert, three for a delete.

    In practice Looser balance, fewer rotations. This is what most standard libraries' ordered maps are.

    B-tree

    Wide nodes sized to a disk page or a cache line

    worst casedisk-friendly

    OperationAvgWorst
    Searchlog nlog n
    Insertlog nlog n
    Deletelog nlog n
    Range scan (k results)log n + klog n + k
    Sequential scannn
    Spacen
    Why?

    Same asymptotics as a balanced binary tree, but a node holds hundreds of keys, so the depth is three or four instead of thirty. Every relational index you have ever used is one of these.

    In practice The log is base B, not base 2. With B ≈ 100 a billion keys sit four levels deep — four disk reads.

    Skip list

    Layered linked lists; balance comes from coin flips

    expected

    OperationAvgWorst
    Searchlog nn
    Insertlog nn
    Deletelog nn
    Range scan (k results)log n + kn
    Min / max11
    Spacen
    Why?

    Balanced-tree behaviour with no rotations, which makes lock-free concurrent versions far easier to write. The worst case is improbable rather than impossible.

    In practice Randomised, not guaranteed. Much easier to make lock-free than a balanced tree, which is why concurrent maps like it.

    Trie

    Keyed by prefix, one node per character. m is key length

    worst case

    OperationAvgWorst
    Searchmm
    Insertmm
    Deletemm
    All keys with a prefixm + km + k
    Longest prefix matchmm
    Spacenk
    Why?

    Independent of how many keys are stored — only the key length matters. Space is the catch: a node per character per branch, unless you compress runs into a radix tree.

    In practice Independent of n, but the constant is a pointer array per node. Compressed variants trade code complexity for a lot of memory.

    Segment tree

    Range aggregates over an array that keeps changing

    worst case

    OperationAvgWorst
    Buildnn
    Range querylog nlog n
    Point updatelog nlog n
    Range update (lazy)log nlog n
    Spacen
    Why?

    Answers "sum/min/max over [i, j]" in log time while updates keep arriving. A prefix-sum array does the query in constant time but cannot be updated.

    In practice Handles any associative combine — min, max, gcd, matrices. That generality is why it costs about 4n memory.

    Fenwick tree

    Prefix sums, in one array and a dozen lines of code

    worst casecache-friendly

    OperationAvgWorst
    Buildnn
    Prefix sumlog nlog n
    Range sumlog nlog n
    Point updatelog nlog n
    Spacen
    Why?

    Half the memory and a fraction of the code of a segment tree, at the cost of only supporting invertible operations — sums yes, minimums no.

    In practice Same log n as a segment tree with a tiny constant and exactly n words, but only for invertible operations like sums.

    KD tree

    Space partitioned one dimension at a time

    average

    OperationAvgWorst
    Buildn log nn log n
    Nearest neighbourlog nn
    Range search√nn
    Insertlog nn
    Spacen
    Why?

    Degrades to a full scan as dimensions rise: past roughly twenty, brute force is faster. High-dimensional search uses approximate methods instead.

    In practice Degrades to a linear scan past about ten dimensions — the curse of dimensionality, visible right in the bound.

    Back to the section menu ↑

    Heaps

    Only the extreme element is cheap to reach. What separates these three is the cost of merging and of decrease-key.

    Binary heap

    A complete tree living in a flat array

    worst casecache-friendly

    OperationAvgWorst
    Find min11
    Insertlog nlog n
    Extract minlog nlog n
    Decrease keylog nlog n
    Build from n itemsnn
    Merge two heapsnn
    Searchnn
    Spacen
    Why?

    Building from a whole array is linear, not n log n — the standard heapify does most of its work near the leaves, where subtrees are shallow.

    In practice An array, no pointers, no allocation. Beats theoretically better heaps up to surprisingly large n.

    Binomial heap

    A forest of binomial trees; merging is the point

    worst case

    OperationAvgWorst
    Find minlog nlog n
    Insert1log n
    Extract minlog nlog n
    Decrease keylog nlog n
    Merge two heapslog nlog n
    Spacen
    Why?

    In practice Mergeable in log n, which a binary heap cannot do — but every operation carries pointer chasing.

    Fibonacci heap

    Amortised bounds; the theoretical best for Dijkstra

    amortised

    OperationAvgWorst
    Find min11
    Insert11
    Extract minlog nn
    Decrease key1log n
    Merge two heaps11
    Spacen
    Why?

    Constant-time decrease-key is what drops Dijkstra to O(E + V log V). The constants are bad enough that a binary heap usually wins in practice — a good reminder that this whole table hides them.

    In practice The famous O(1) decrease-key is amortised and the constants are large. Dijkstra with a binary heap usually beats Dijkstra with a Fibonacci heap on real graphs.

    Back to the section menu ↑

    Disjoint sets

    Grouping things that merge and never split.

    Union-Find

    With path compression and union by rank

    amortised

    OperationAvgWorst
    Make set11
    Findα(n)α(n)
    Unionα(n)α(n)
    Connected?α(n)α(n)
    Count components11
    Spacen
    Why?

    α is the inverse Ackermann function — below five for any n that fits in memory. Constant for every practical purpose, and provably not quite constant.

    In practice Path compression plus union by rank. Either one alone is log n; together they are effectively constant.

    Back to the section menu ↑

    Sorting

    Space is the extra memory, not the array. The badges are what people forget: stable keeps equal elements in their original order, in place needs no second array, adaptive gets faster on data that is already nearly sorted.

    Algorithm Best Average Worst Space Properties
    Quicksort n log n n log n log n in place
    The quadratic case needs a pathological run of pivots; random or median-of-three pivots make it vanishingly unlikely. Fastest in practice on general data.In practice Its constant factor is the smallest of the O(n log n) sorts, which is why the quadratic worst case is tolerated everywhere.
    Mergesort n log n n log n n log n n stable
    No bad case. You pay for it in the scratch buffer, which is also what makes it the sort of choice for linked lists and for data too large to fit in memory.In practice The extra array is the cost people forget; on large data the allocation and the copy dominate the comparison count.
    Timsort n n log n n log n n stableadaptive
    Finds runs that are already ordered and merges them. Real data is full of such runs, which is why this is the default sort in Python, Java and Rust.In practice Real input is often already partly sorted, so the "best case" is not a curiosity — it is Tuesday.
    Heapsort n log n n log n n log n 1 in place
    The only common sort that is both in place and n log n in the worst case. Poor cache locality keeps it off the podium anyway.In practice Optimal bounds, poor locality: it jumps around the array by powers of two and loses to quicksort in practice.
    Introsort n log n n log n n log n log n in place
    Quicksort that watches its own recursion depth and switches to heapsort when it gets too deep, and to insertion sort near the leaves. This is what your standard library actually runs.
    Insertion sort n 1 stablein placeadaptive
    Unbeatable below about twenty elements. Every serious quicksort falls back to it near the leaves.
    Bubble sort n 1 stablein placeadaptive
    Teaching material. Insertion sort is strictly better and just as simple.
    Selection sort 1 in place
    Quadratic even on sorted input, but it performs only n writes — worth knowing when writes are the expensive part, as on flash.
    Shell sort n log² n n log² n n log² n 1 in placeadaptive
    The gap sequence is the whole algorithm. These figures are for Pratt's gaps — every 2^i·3^j below n — which are provably n log² n. Ciura's empirically-tuned gaps are faster on real input and have no proven bound at all, which is why they are not what is printed here.
    Tree sort n log n n log n n stable
    Counting sort n+k n+k n+k k stable
    k is the size of the key range. Linear for small integer keys, hopeless for 64-bit ones.In practice Linear in n + k, and k is the *key range*. Sorting 32-bit integers this way asks for a four-billion-entry table.
    Radix sort nk nk nk n+k stable
    Beats n log n only because it never compares — k is the number of digits. It sorts keys, not arbitrary orderings.In practice Beats comparison sorts on fixed-width keys, but the d passes each rewrite the whole array.
    Bucket sort n+k n+k n stable
    Linear only while keys spread evenly across buckets. Clustered keys collapse it to the worst case.

    Swipe the table →

    Back to the section menu ↑

    Graphs

    V is vertices, E is edges. On a sparse graph E ≈ V; on a dense one E ≈ V², and the two columns diverge sharply.

    Algorithm Time Space
    Shortest path when every edge costs the same. Weighted edges need Dijkstra.In practice Shortest path only when every edge costs the same. With weights it is confidently wrong, not merely slow.
    Space is the recursion stack, which on a path-shaped graph is every vertex.
    Topological sort V+E V
    Fails on a cycle, which makes it the standard way to detect one.
    Dijkstra (binary heap) (V+E) log V V
    Negative edge weights break it silently — it returns a path, just not the shortest one.In practice Fails outright with negative edge weights — it commits to a vertex the moment it pops it.
    Bellman-Ford VE V
    Slower than Dijkstra, but handles negative weights and reports negative cycles.In practice Slower, but it tolerates negative weights and detects negative cycles. That is what you are buying.
    Floyd-Warshall
    All pairs at once, in three nested loops. Fine to a few thousand vertices, hopeless past that.In practice Three nested loops over a dense matrix: terrible asymptotics, excellent constants. Under a few hundred vertices it beats running Dijkstra V times.
    Dijkstra with a heuristic pulling it toward the goal. Same bound, and the constant is what you came for — a good heuristic visits a fraction of the vertices. A heuristic that overestimates costs correctness, not just speed.In practice Only as good as the heuristic. With h = 0 it is Dijkstra; with an inadmissible h it is fast and wrong.
    Kruskal's MST E log E V+E
    Sort every edge, then union-find to skip the ones that would close a cycle.
    Prim's MST E log V V
    Preferable to Kruskal on dense graphs, where sorting every edge dominates.
    Tarjan SCC V+E V
    Strongly connected components in a single depth-first pass.
    Edmonds-Karp max flow VE² E
    Ford-Fulkerson with breadth-first search picking the augmenting paths, which is what bounds it independently of the capacities. The number of augmenting paths depends on the graph's flow structure, not on V and E alone.

    Swipe the table →

    Back to the section menu ↑

    Pattern matching

    n is the text, m is the pattern. Preprocessing the pattern is what buys the linear scan.

    Algorithm Preprocess Match Space
    Brute force nm 1
    Fine for short patterns, and what your language's indexOf often does.
    Knuth-Morris-Pratt m n m
    Never re-reads a character of the text. The failure table records where to resume after a mismatch.
    Boyer-Moore m + k n m + k
    Scans the pattern backwards and can skip m characters at a time, so it often reads only a fraction of the text. Two tables do it: the bad-character rule, one entry per alphabet symbol, and the good-suffix rule, one per pattern position. The bad-character rule alone is Boyer-Moore-Horspool, whose worst case is nm.In practice Sublinear in practice — it skips forward by whole pattern lengths — while the good-suffix rule is what keeps the worst case linear. The reason grep is fast.
    Rabin-Karp m n 1
    A rolling hash makes the average linear; hash collisions push the worst case to nm. The natural choice for searching many patterns at once.In practice The hash makes it linear in expectation; a collision-heavy input degrades it to the brute-force product.
    Z-algorithm n+m n+m n+m
    One pass computing, for every position, the longest prefix match. Simpler to get right than KMP.
    Aho-Corasick m n m
    All patterns at once, in one pass, in time independent of how many there are. m here is the total length of every pattern.In practice One pass finds every pattern at once, which is why intrusion detection and spam filters use it.

    Swipe the table →

    Back to the section menu ↑

    Dynamic programming

    The classics, with the table dimensions that produce the bound. Space is usually reducible to one row of the table.

    Problem Time Space
    Fibonacci n 1
    The naive recursion is 2ⁿ. Memoising it is the whole idea of dynamic programming in one example.In practice The naive recursion is exponential only because it recomputes; one array turns it linear. This is the whole idea of DP in one row.
    0/1 knapsack nW W
    Pseudo-polynomial: W is a value, not an input length, so this is exponential in the number of bits of W. The problem is still NP-hard.In practice O(nW) is pseudo-polynomial: W is a value, so the cost is exponential in the number of bits used to write it down.
    Longest common subsequence nm m
    The engine behind diff.
    Edit distance nm m
    Same table as LCS with different costs. What spell-checkers and fuzzy search run on.
    Longest increasing subsequence n log n n
    The obvious DP is n²; patience sorting with binary search brings it down.
    Matrix chain multiplication
    Choosing where to put the brackets, not doing the multiplications.
    Rod cutting n
    Coin change nk k
    k is the target amount. Greedy works only for well-behaved coin systems; this always works.
    Subset sum nk k
    Pseudo-polynomial, like knapsack, and NP-complete for the same reason.In practice Same trap as knapsack — polynomial in the target, exponential in its encoding.

    Swipe the table →

    Back to the section menu ↑

    How much input fits?

    The plot asks how long a given n takes. This asks the question you usually have instead: given a time budget, how large can n be?

      “Operation” is an illustrative unit, not a benchmark — a comparison, a pointer hop, a hash. What matters is the distance between the rows, and no machine changes that.

      Describe the workload

      Seven switches, two ranked answers, and the reasons the runners-up lost. A slower, more honest version of the need list.

      Read-heavy or write-heavy?
      Do you need sorted iteration or range queries?
      Do you repeatedly take the smallest or largest item?
      Are the keys trusted?
      Where does the data live?
      Anything special about the keys?
      Concurrent access?

      Learn mode

      The same material, in the order that makes sense the first time. Seven lessons, five to ten minutes each, every one ending in three questions.

      Growth and notation

      Lesson 1 of 7

      What a complexity actually claims, and what it deliberately refuses to say.

      A bound is a shape

      A complexity describes how the work responds to a bigger input. It is a claim about a ratio: double n, and an O(n) routine does about twice the work while an O(n²) routine does about four times. Wall-clock time never enters into it, which is why the same bound holds on a phone and on a server.

      Why constants are dropped

      Write out the real count — say 3n² + 2n + 7 — and ask what happens at n = 1,000. The square term is 3,000,000; the rest is 2,007. Past some input size the largest term is the answer and everything else is rounding, so the notation keeps only that term and drops its coefficient too.

      O, Ω and Θ

      O is an upper bound: no worse than this. Ω is a lower bound: no better than this. Θ is both at once — the growth *is* this. Casual usage says "big-O" for all three, which is harmless right up until someone says "quicksort is O(n²)" and means it as a criticism.

      Best, average, worst, amortised

      These pick which input you are talking about, not which notation. Worst case is over all inputs; average is over a distribution of them; amortised is over a *sequence* of operations, and is a guarantee rather than a statistical hope.

      Three questions

      An algorithm does 5n + 200 operations. Its complexity is:

      You double n and the running time goes up about eightfold. The likely class is:

      Which describes a guarantee over a sequence of operations rather than over inputs?

      Predict, then reveal

      Curves are pleasant to look at and easy to forget. Commit to an answer first — the gap between what you expected and what happened is the part that sticks.

      1. n log n versus n². Which wins, and by how much at a million?

        Predict:
      2. A linear algorithm with a constant of 1,000 against a quadratic one with a constant of 1. Where do they cross?

        Predict:
      3. A logarithmic lookup: how much slower at a trillion than at a thousand?

        Predict:
      4. Two O(n) passes over the same array. Can one be ten times slower?

        Predict:
      5. O(2ⁿ) at n = 40, 50 and 60. How bad does it get?

        Predict:
      6. Trial division to n versus to √n, for primality at n = 1,000,000.

        Predict:

      Derive the Big-O

      A few lines of code and one question: how many times does the work happen? Reading a table is recognition; this is the skill the table stands in for.

      1. One loop

        total = 0
        for i in 0..n-1:
          total += a[i]

        How many times does the addition run, and what is the class?

      2. Nested loops

        count = 0
        for i in 0..n-1:
          for j in 0..n-1:
            count += 1

        How many times does count += 1 run?

      3. The triangular loop

        count = 0
        for i in 0..n-1:
          for j in i+1..n-1:
            count += 1

        The inner loop shrinks each pass. What is the class?

      4. Halving the input

        i = n
        while i > 1:
          i = i / 2
          step()

        How many times does step() run?

      5. Two loops in a row

        for i in 0..n-1:
          step()
         
        for j in 0..n-1:
          step()

        Sequential loops, not nested. The class is:

      6. Divide and conquer with a linear merge

        sort(a):
          if a.len <= 1: return a
          l = sort(first half)
          r = sort(second half)
          return merge(l, r)   # linear in a.len

        Linear work at each level, halving each time. Total?

      7. A log inside a loop

        for i in 0..n-1:
          binary_search(sorted, a[i])   # log n

        A linear loop whose body is logarithmic:

      8. The early exit

        for i in 0..n-1:
          if a[i] == target:
            return i     # maybe immediately
        return not_found

        The return can fire on the first element. What is the worst case?

      9. Simplify the expression

        # counted by hand:
        # 3n² + 2n + 7 operations

        What is this in Big-O?

      10. The hidden copy

        s = ""
        for i in 0..n-1:
          s = s + "x"     # builds a NEW string

        With immutable strings, what does this cost?

      Flashcards, both directions

      Most practice runs name → complexity. The direction engineering asks in is the reverse: here is my requirement, what fits? Both are here.

      Your progress

      Kept in this browser and nowhere else — no account, no server, nothing anyone has to delete later. Export it if you want it elsewhere.

      Reading the sheet

      O upper bound
      It will not do worse than this. The one people mean when they say “big-O”.
      Ω lower bound
      It will not do better than this — the best case, on the input it likes most.
      Θ tight bound
      Upper and lower agree. The growth is this, not merely bounded by it.
      n and its friends
      n is the number of elements; m a pattern or key length; k an output size or key range; V and E a graph's vertices and edges. Focus or tap any chip to see what its symbols mean in that particular row.

      Every symbol

      n
      The number of elements — items in the structure, keys in the map.
      m
      A second size: the length of a pattern or key, or the edge count in some texts.
      k
      An output size — how many results a query actually returns.
      V
      A graph's vertices.
      E
      A graph's edges.
      W
      The largest edge weight, or the width of a numeric range.
      α(n)
      The inverse Ackermann function. Below 5 for any n you can store, so it is 'constant' in every practical sense.
      B
      A disk block or cache line — how many keys travel together in one read.
      d
      The number of dimensions, or the alphabet size, depending on the section.

      Guarantee badges

      worst case
      The bound holds for every input, with no assumptions.
      expected
      Holds on average over the algorithm's own randomness, not over your data.
      average
      Holds on typical data. Adversarial input can be worse.
      amortised
      Any run of operations averages to this, though one of them may be far worse.
      cache-friendly
      Touches memory in order. Often several times faster than the same bound with pointer chasing.
      disk-friendly
      Designed so one block read does a lot of work — the reason B-trees run databases.
      stable
      Equal elements keep their original order.
      in place
      Constant extra memory.
      adaptive
      Faster on nearly-sorted input.

      Inside the cards the O( ) is dropped: the column heading already says which bound you are reading, and two hundred repetitions of the same three characters is ink you have to look past. Constants are dropped too, so a linear pass that touches every byte twice and one that touches it once share a colour. At small n the constant is usually what you feel; this sheet is about what you feel later.

      Keyboard

      /
      Jump to the search box
      J / K
      Move to the next or previous question
      Space
      Flip the flashcard, while the deck is on screen
      1 / 2
      Grade it — needs review, or got it
      Esc
      Close the comparison tray, or clear the highlighted class
      ?
      Show this