Skip to content

Time and space complexity

Big-O notation names how fast a cost in time or memory grows, ignoring constant factors and small inputs. The common classes are 1, log n, n, n log n, n² and 2ⁿ.

Helps to know firstCounting steps

Helps to know first
This topic

First, the picture

Is an O(n) algorithm always faster than an O(n²) one? Watch six costs race as n doubles, tangled at first, then far apart.

How fast different costs grow

Six growth classes as n doubles from 2 to 1024

n = 2: the axis ends at 2 n log₂ n = 4. Every curve fits.

1
1
log₂ n
1
n
2
n log₂ n
2
n²
4
2ⁿ
4
1024
0.00 / 9.00 s
Describe this picture

A plot with no code. Six curves, one per growth class, are drawn from n = 1 to the current n: 1 (dashed) and log₂ n in the third colour, n and n log₂ n (dashed) in the first, n² and 2ⁿ (dashed) in the second, named in a legend above the plot. The clip doubles n from 2 to 1024, one beat each, about 10 seconds. Both axes rescale at every beat: across to n, and up to 2 n log₂ n, so the n log₂ n curve always ends at half height. At n = 2 and 4 every curve fits, n² and 2ⁿ ending exactly at the top. From n = 8 on, n² and 2ⁿ rise out of the frame, while 1 and log₂ n flatten onto the floor and n sinks below n log₂ n. Under the plot are one sentence on the current n and six readouts, one per class, with its value at that n; at 1024 they read 1, 10, 1,024, 10,240, 1,048,576 and 2¹⁰²⁴ ≈ 1.8 × 10³⁰⁸. After the clip, the slider “n” (2 to 1024 in doublings) stops it at one n. The transport plays and pauses, steps one beat back or forward, and scrubs; n and a paused step are kept in the page address as growth.n and growth.step.

Growth, not time

Counting steps gave each algorithm a count: n steps for one loop, n(n − 1)/2 for all pairs. Big-O keeps only the part of a count that decides how it grows for large n. It throws away constant factors, which depend on the machine, and smaller terms, which stop mattering once n is large.

The clip shows why only large n counts. At n = 4, n² and 2ⁿ are both 16, and n log₂ n is 8: the order of the curves at small n is not their order at large n. A cost of 100n is larger than n² for every n below 100. Big-O is about what happens after the curves stop crossing.

The code and its cost

The definition

f(n) = O(g(n)) when there are a constant c > 0 and a starting point n₀ such that

f(n)≤c g(n)for every n≥n0.f(n) \le c\,g(n) \quad \text{for every } n \ge n_0.

From n₀ on, the curve of f stays under the curve of c·g. The definition says nothing about n below n₀, and it is an upper bound: 100n is O(n²), but n² is not O(100n). Big-O gives a growth rate, not a run time: two O(n) programs can differ in speed by any constant factor.

Six classes

ClassNameTypical code
O(1)constantread one array cell
O(log n)logarithmica loop that halves what is left (binary search)
O(n)linearone loop over the input
O(n log n)n log na halving loop inside a loop over n; good sorts
O(n²)quadratica loop over all pairs
O(2ⁿ)exponentialtry every subset

log₂ n is small: it is about how many times n can be halved before it reaches 1, about 10 for a thousand (9.97) and about 20 for a million (19.93). It is nothing like n.

Reading a class off code

  • Drop constants and smaller terms. 3n² + 5n + 2 is O(n²); 2n is O(n).
  • Steps in sequence add, and the larger term wins: an O(n) loop followed by an O(n²) loop is O(n²).
  • Nested loops multiply: a loop over n inside a loop over n is O(n²).
  • A loop that halves or doubles its variable runs about log₂ n times.

Space

Space complexity counts the extra memory a function needs beyond its input. A function that sorts an array where it lies, with a few variables, uses O(1) extra: it works in place. A function that makes a copy of its n inputs uses O(n). The stack counts too: a recursion that goes n calls deep keeps n frames at once, so it uses O(n) memory even if each frame is small. Chapter 4 draws those frames.

Worked example

Checking the definition. f(n) = 3n² + 5n + 2 is O(n²). With c = 10 and n₀ = 1 it works, since 5n ≤ 5n² and 2 ≤ 2n² for n ≥ 1, so f(n) ≤ 3n² + 5n² + 2n² = 10n². A tighter c needs a later start. With c = 4: at n = 5, f(5) = 102 > 100 = 4 · 5²; at n = 6, f(6) = 140 ≤ 144. From there on 4n² − f(n) = n² − 5n − 2 only grows, so n₀ = 6 is the smallest start for c = 4.

Reading loops.

for (i = 0; i < n; i++)
    for (j = 0; j < n; j++)
        ...                      /* n · n: O(n²) */

for (i = 1; i < n; i *= 2)
    ...                          /* about log₂ n times: O(log n) */

The first is n² steps. The second runs ⌈log₂ n⌉ times: for n = 1000, i takes the values 1, 2, 4, …, 512, which is 10 times. Put that halving loop inside a loop over n and the total is O(n log n). A loop to n followed by another loop to n is 2n steps, which is O(n).

What a million costs. Take n = 10⁶ and a machine doing 10⁹ steps a second. log₂ n ≈ 20 steps, no time at all. n log₂ n ≈ 2 × 10⁷ steps: 0.02 seconds. n² = 10¹² steps: about 1,000 seconds, some 17 minutes. 2ⁿ is out of reach long before a million: at n = 60, 2⁶⁰ ≈ 1.15 × 10¹⁸ steps take about 36 years.

Three checks. Is 100n = O(n²)? (Yes: O( ) is an upper bound.) Is n² = O(100n)? (No.) What is the class of for (i = n; i > 0; i /= 2)? (O(log n).) How much extra space does the sum in Arrays and pointers need? (O(1): a total and an index.)

Where it’s used

Library documentation. Sorting, searching and hash tables are described by their costs: a sort in O(n log n), a lookup in O(1) on average, a binary search in O(log n).

The FFT. The direct DFT of n samples takes n² steps and the fast Fourier transform n log₂ n; at n = 1024 that is about 10⁶ against 10⁴. The FFT is why spectra are cheap to compute.

Design reviews. “This is quadratic in the number of users” is how a slow design is caught before it ships.

Reference card

Code or ideaMeaning
f(n) = O(g(n))for some c > 0 and n₀, f(n) ≤ c·g(n) for every n ≥ n₀
dropconstant factors and smaller terms
sequenceadd: the larger class wins
nestingmultiply
halving loopabout log₂ n times
in placeO(1) extra memory
n = 10⁶log₂ n ≈ 20, n log₂ n ≈ 2 × 10⁷, n² = 10¹²

Platform: C11, gcc on x86-64 Linux: int 4 bytes, pointers 8, little-endian. Addresses in the pictures are short stand-ins, by one rule for the whole course; sizes are the real ones.

End of the lesson

Where to go next.

New lessons by email

Are you 13 or over?

Accounts and email updates are for people aged 13 or over. We keep only your answer, never your date of birth.

Phasorium

Account

Sign inWith Google, GitHub or your email

Subjects

Site

Look