What Big-O measures

Read and write Big-O honestly: what it promises, what it hides, and when the "slower" algorithm is the right one.

01of 24
≈ 12Min read
5Parts

It is about growth, not speed

Big-O answers one question: when the input gets bigger, what happens to the work? It says nothing about milliseconds. An O(n²) algorithm can beat an O(n log n) one for the sizes you actually have, and often does.

The reason it is worth learning anyway is that growth is the part you cannot fix later. A constant factor is a faster machine or a tighter loop; a bad growth rate is a program that works in testing and dies in production.

The rates you will meet

The jump that matters most is between O(n log n) and O(n²), because that is the line between "handles the whole dataset" and "was fine on the test fixture". A million items at n log n is twenty million operations; at n² it is a trillion.

And O(log n) barely grows at all: a thousand items cost ten probes, a million cost twenty. That is why binary search and balanced trees are everywhere.

GrowthNamen = 1000n = 1 000 000
O(1)constant11
O(log n)logarithmic1020
O(n)linear1 0001 000 000
O(n log n)linearithmic10 00020 000 000
O(n²)quadratic1 000 00010¹² — a week
O(2ⁿ)exponential10³⁰¹ — nevernever

The rules for writing it

Rule four is the one people get wrong in interviews and in production: two loops with different inputs multiply their own sizes. Name them, and the analysis stops being guesswork.

js
/* 1. Drop the constants: 3n + 50 is O(n) */
for (const x of items) { a(x); b(x); c(x); }          // O(n), not O(3n)

/* 2. Keep only the fastest-growing term: n² + n + 1000 is O(n²) */
for (const x of items) for (const y of items) pair(x, y);
for (const x of items) single(x);                      // O(n² + n) = O(n²)

/* 3. Sequential is addition, nested is multiplication */
sort(items);                 // O(n log n)
for (const x of items) …     // + O(n)   → O(n log n)
for (const x of a) for (const y of b) …   // O(a × b), NOT O(n²) unless they are the same n

/* 4. A different input is a different n */
for (const word of words) for (const ch of word) …     // O(total characters), not O(words²)

Best, average and worst

Quicksort is the default sort almost everywhere despite an O(n²) worst case, because the average is excellent and its constant factor is small. Insertion sort is O(n²) and still the right choice below about a dozen items — which is why every real sort switches to it at the bottom.

BestAverageWorst
Quicksortn log nn log nn² — on already-sorted input, with a naive pivot
Hash lookup11n — when every key collides
Insertion sortnn²n²
Binary search1log nlog n

What the notation throws away

So the honest summary is: use Big-O to choose the approach, and a profiler to choose the implementation. The performance course is about the second half.

  • Constants. An O(n) pass that touches the disk loses to an O(n log n) one in memory, every time.
  • Memory locality. Walking an array beats walking a linked list of the same length, because the array is one cache line after another. The notation cannot see this, and it is often a factor of ten.
  • Space. Merge sort and quicksort are both O(n log n) in time; one needs O(n) extra memory and the other O(log n). On a big array that decides it.
  • The actual n. Below a few hundred items, almost nothing in this course matters. Above a few million, almost all of it does.

Practice