Big-O notation, explained with growth curves
Big-O notation describes how the work an algorithm does grows as its input gets bigger. It deliberately ignores constant factors and small inputs, so you can compare algorithms without timing them on a particular machine.
On this page
Suppose one program takes 3 seconds on a list of 10,000 items and another takes 5. Which is better? You can’t tell yet. If the first program’s running time grows in proportion to the square of the input size and the second grows in proportion to the size, the second wins comfortably once the list reaches a million items, whatever their timings at 10,000. Big-O is the vocabulary for that kind of statement.
Count steps, not seconds
Seconds depend on the processor, the language, the compiler and what else the computer is doing. So instead we pick a basic step, something that takes a bounded amount of time such as a comparison, an addition or an array access, and count how many of them the algorithm performs as a function of the input size .
For loops this is mostly a matter of multiplying. A loop that runs times with a constant amount of work inside costs about steps. A loop inside a loop, both running times, costs . A loop that halves its counter each time runs only about times, because that is how many halvings it takes to get from down to 1.
The stepper below runs five common loop shapes. Every square is one execution of the innermost line. Step through a small case, then drag up and watch how differently the totals grow.
Count the stepsEach square is one run of the innermost line. Change n and compare the total with the formula.
Pseudocode
Variables
Total
Notice the triangle loop: its inner loop runs times, for a total of . That is roughly half of the full nested loop, but it still grows like . Doubling roughly quadruples the work in both. That shared growth rate is what Big-O captures.
The definitions
Let be the number of steps an algorithm takes and a simple comparison function such as .
means there are constants and such that for every . It is an upper bound: eventually, grows no faster than a constant multiple of .
means for all , for some . It is a lower bound.
means both hold: grows at exactly the rate of , up to constant factors.
The “=” sign is a long-standing abuse of notation: is really a set of functions, and means belongs to it. Donald Knuth’s 1976 note in SIGACT News is the reason computer scientists use Ω and Θ the way they do today.
Worked example:
Upper bound. We want . Try : the inequality becomes . At the left side is 27 and the right is 25, so it fails; at it is , and from there pulls away. So , works, and .
Lower bound. For every , , so , shows it is .
Both bounds hold, so it is . The and the factor 3 are absorbed by the constants.
A quicker test, when the limit exists: if is a finite positive number then ; if it is 0 then but not . Here .
The usual growth classes
| Class | Name | Typical example |
|---|---|---|
| constant | Reading A[i]; pushing onto a stack | |
| logarithmic | Binary search on a sorted array | |
| linear | Finding the largest value in an unsorted list | |
| linearithmic | Merge sort and heapsort; any comparison sort’s best possible worst case | |
| quadratic | Insertion sort in the worst case; checking every pair | |
| cubic | Multiplying two matrices the schoolbook way | |
| exponential | Trying every subset of items | |
| factorial | Trying every ordering, as in brute-force travelling salesman |
The chart puts them side by side. With a linear vertical axis the exponential and factorial curves leave the top of the chart almost at once; switch to a logarithmic axis to keep them in view. The table on the right turns step counts into time at a billion steps per second, which is generous for most real code.
At , an algorithm needs about ten thousand steps, a quadratic one a million, and a cubic one a billion: microseconds, a millisecond and a second. An exponential algorithm needs about steps, which no computer will ever finish. That gap is why improving the growth rate matters far more than shaving constants.
Rules of thumb
- Drop constants and lower-order terms. .
- Sequential parts add; nested parts multiply. A linear pass followed by a quadratic one is . A linear loop around a logarithmic one is .
- The base of a logarithm doesn’t matter. , a constant multiple, so we just write .
- Recursive algorithms give recurrences. Merge sort satisfies , which solves to . The recursion tree visualiser solves recurrences of this shape and shows why.
Common misunderstandings
Big-O is not “the worst case”
Best, average and worst case are about which inputs you consider. O, Ω and Θ are about how you bound a function. They are independent: insertion sort’s best case (already sorted input) is and its worst case (reversed input) is . You can see both in the sorting visualiser by choosing “Reversed” and then sorting the result again.
An upper bound can be loose
Technically linear search is , since anything bounded by is also bounded by . That statement is true but not useful. When people say “O” informally they usually mean the tightest bound they know, which is really Θ.
Constants still matter in practice
Asymptotic notation says nothing about small inputs. For short arrays, insertion sort often beats merge sort because its inner loop is so simple, which is why library sorting routines switch to insertion sort for small pieces. Use Big-O to rule out approaches that cannot scale, then measure.
Averages over a sequence of operations
Appending to a dynamic array occasionally costs , when the array is full and must be copied into a bigger one. If the capacity doubles each time, the total cost of appends is still , so each append is amortised. Amortised analysis gives guarantees for sequences of operations rather than single ones.
References
- D. E. Knuth, “Big Omicron and big Omega and big Theta”, ACM SIGACT News 8(2), 1976. doi:10.1145/1008328.1008329
- D. D. Sleator and R. E. Tarjan, “Amortized efficiency of list update and paging rules”, Communications of the ACM 28(2), 1985. doi:10.1145/2786.2793
- T. H. Cormen, C. E. Leiserson, R. L. Rivest and C. Stein, Introduction to Algorithms, 4th ed., MIT Press, 2022. Chapter 3 covers asymptotic notation.
- R. L. Graham, D. E. Knuth and O. Patashnik, Concrete Mathematics, 2nd ed., Addison-Wesley, 1994. Chapter 9 treats asymptotics in depth.