What a bound is

What O-notation does not say

Big-O is a statement about a limit. It does not say how fast, it does not say which is better, it does not say anything at all about any particular n, and it discards precisely the factor that usually decides the answer. Knowing exactly what it claims is the difference between using it and being misled by it.

f(n)=O(g(n))f(n) = O(g(n)) means: there exist constants c>0c > 0 and n0n_0 such that f(n)cg(n)f(n) \le c \cdot g(n) for all nn0n \ge n_0.

That is the whole definition. Everything people believe about big-O that is not in that sentence is something they added.

The sentence contains two escape hatches, and almost every misuse of the notation comes from forgetting one of them. The first is cc: an arbitrary constant, unbounded, chosen after the fact to make the inequality work. The second is n0n_0: an arbitrary threshold, also unbounded, below which the statement says nothing whatsoever.

So “merge sort is O(nlogn)O(n \log n)” says that beyond some size, merge sort’s cost stays under some multiple of nlognn \log n. It does not say the multiple. It does not say the size. And below that size it is not a claim at all.

Comparison counts against n, random inputMeasured counts on logarithmic axes, where a power law is a straight line and its exponent is the slope. The quadratic algorithms rise at twice the gradient of the linearithmic ones, and the vertical gaps between the parallel lines are the constants the notation discards.10010³10010³10⁴10⁵10⁶ncomparisonsInsertionHeapsortMergeShellsorta power law is a straight line herecomparisons, counted exactly
Fig. 1 Four algorithms, measured. Three are Θ(n log n) and one is Θ(n²). On these logarithmic axes the classification is the slope, and it is genuinely visible — but so are the vertical gaps between the three parallel lines, and those gaps are exactly what the classification discards.

The definition, walked through slowly

It repays a careful reading, because every misuse is a misreading.

There exist constants c>0c > 0 and n0n_0 such that f(n)cg(n)f(n) \le c \cdot g(n) for all nn0n \ge n_0.

“There exist.” Not “for the constant anyone would expect”, not “for a reasonable constant”. Any constant at all. An algorithm that performs 1040n10^{40} \cdot n operations, it is O(n)O(n), and it is linear, and it is also useless. The notation has no opinion about the size of cc and cannot be made to have one.

“For all nn0n \ge n_0.” The behaviour below n0n_0 is unconstrained. An algorithm that performs 2n2^n operations for n<100n < 100 and nn operations thereafter is O(n)O(n). This sounds artificial until one notices that it describes several real algorithms with expensive precomputation, and exactly describes any algorithm with a large fixed setup cost.

f(n)cg(n)f(n) \le c \cdot g(n). One-sided. This is the part that makes OO so much weaker than people use it as. Since nlognn2n \log n \le n^2 for n2n \ge 2, merge sort is O(n2)O(n^2), and saying so is not a mistake — it is a true statement that happens to waste most of what is known.

The consequence of all three together: OO is a very weak claim, and it is routinely used to make a strong one. When somebody rejects an algorithm because it is O(n2)O(n^2), they are usually reasoning as though the bound were tight, the constant were ordinary and n0n_0 were small. Often all three are true. The notation does not say so.

O, Θ and Ω, and why the distinction is not pedantry

Three symbols get used where one is meant, and the sloppiness costs something real.

f=O(g)f = O(g)ff grows no faster than gg. An upper bound. Merge sort is O(nlogn)O(n \log n); it is also O(n2)O(n^2), and O(n100)O(n^{100}), and O(2n)O(2^n). All of those are true statements. Only the first is useful, and the notation does not distinguish useful upper bounds from vacuous ones.

f=Ω(g)f = \Omega(g)ff grows no slower than gg. A lower bound. Merge sort is Ω(nlogn)\Omega(n \log n) and also Ω(n)\Omega(n) and Ω(1)\Omega(1).

f=Θ(g)f = \Theta(g) — both. ff grows at the same rate as gg, up to constants. This is almost always the statement people intend when they write OO, and it is strictly stronger.

The practical consequence: when somebody says an algorithm is O(n2)O(n^2), they may mean that it has been proved at worst quadratic, or they may mean that it is quadratic. The first leaves room for it to be much better than that. The second does not. The notation does not distinguish them, and the difference matters when the claim is being used to reject an algorithm.

This site uses Θ\Theta where it means Θ\Theta, and its fitting machinery tests for Θ\Theta specifically — the ratio count/f(n)\text{count}/f(n) must be bounded above and below, which is exactly the two-sided condition. An algorithm that satisfied only the upper bound would show a ratio drifting downwards, and would be refused.

What the notation throws away

Four things, in increasing order of how often they matter.

The constant factor. Θ(nlogn)\Theta(n \log n) says the count is some multiple of nlognn \log n. Merge sort’s multiple is 0.838. Heapsort’s is 1.613. Both are correct and the second is 1.9 times the first, forever, at every size. That factor is the entire difference between the two algorithms by this measure and the notation is designed to be blind to it.

The lower-order terms. Insertion sort’s comparison count on random input is about n2/4n^2/4, and it is exactly n24+\frac{n^2}{4} + terms in nn and constants. At n = 20 the lower-order terms are a large fraction of the total. At n = 20,000 they are irrelevant. Asymptotic notation is about the second regime, and a lot of real code lives in the first.

The threshold n0n_0. The claim begins somewhere and the notation never says where. An algorithm that is O(nlogn)O(n \log n) with n0=1012n_0 = 10^{12} is, for every practical purpose, an algorithm about which nothing has been said.

The input. This one is not usually counted as part of the notation and it does more damage than the others together. “Insertion sort is O(n2)O(n^2)” is a statement about the worst case. On sorted input insertion sort performs n1n - 1 comparisons, which is Θ(n)\Theta(n), and the same code is being described. Unless the input distribution is named, the class is ambiguous by a factor that can be arbitrarily large.

Insertion sort on four kinds of inputThe same algorithm, the same range of n, four input distributions. The best and worst differ by a factor of 1024 at n = 2048, so a single complexity class describes this algorithm only if the input is also stated.10010³10010³10⁴10⁵10⁶ncomparisonsalready sortednearly sortedrandomreversedadaptive — it does almost nothing to an already sorted arraycomparisons, counted exactly
Fig. 2 The same algorithm, the same sizes, four input distributions. At n = 2,048 the best and the worst differ by more than two orders of magnitude. Two of these lines are linear and two are quadratic, and they are all insertion sort. A complexity class without a named input is a claim with a missing argument.

An algorithm in a worse class winning at every practical size

The clean illustration is not hypothetical and it is not exotic.

Consider insertion sort against merge sort. Insertion sort is Θ(n2)\Theta(n^2); merge sort is Θ(nlogn)\Theta(n \log n). The asymptotic verdict is unambiguous and, for large enough n, correct. But asymptotic verdicts are about large enough n, and where the arrays are never large, the verdict is about a situation nobody is in.

Measured on this site, insertion sort’s comparison count crosses merge sort’s between n = 4 and n = 6 — very early, because merge sort is genuinely good at comparing. But the total memory traffic — reads plus writes — tells a different story, and the crossover there sits between n = 12 and n = 16.

Where insertion sort actually wins — and it is not in the comparisonsMean over 60 random inputs at each size, both counts on one pair of axes. Insertion sort performs more comparisons than Merge sort at every size measured, including n = 4: the dashed pair never cross. The solid pair — total reads and writes — do cross, between n = 12 and n = 16. The familiar advice to fall back to insertion sort on small subarrays is right, and the reason is memory traffic rather than comparisons, which is a distinction the usual telling of it loses.481632641281010010³10⁴noperations (mean of 60 runs)Insertion trafficMerge trafficInsertion cmpMerge cmptraffic crossessolid: reads + writes · dashed: comparisonstraffic crosses between n = 12 and 16; comparisons never do
Fig. 3 Both counts for both algorithms on one pair of axes. The dashed lines are comparisons and they never cross: insertion sort makes more comparisons than merge sort at every size measured, including four elements. The solid lines are total reads and writes, and those do cross, around a dozen elements. The familiar advice about small subarrays is right, and it is about the solid pair.

This is the case that gets an essay to itself because it is more interesting than the usual telling. The lesson for the notation is narrower and still worth having: the class names who wins eventually, and “eventually” is doing an enormous amount of work in that sentence.

Real examples of the same shape are everywhere. Matrix multiplication has algorithms with exponents below 2.4, and essentially nobody uses them, because the constants are astronomical and the crossover points are beyond any matrix anyone multiplies. The best known algorithm for a problem and the algorithm worth writing are frequently different algorithms.

Why the notation exists anyway

Having spent a thousand words on its limitations, it is worth saying plainly that asymptotic notation is one of the most useful ideas in the subject and the alternatives are worse.

The alternative is exact counts, and exact counts are unusable for comparison. Insertion sort on random input performs, on average, n2+n24\frac{n^2 + n - 2}{4} comparisons — for a specific model of “random” and a specific way of counting. Change either and the formula changes. Nobody can hold a dozen such formulas in mind, and comparing two of them means comparing two polynomials, which mostly means looking at the leading term. Which is the classification.

The notation is a deliberate discarding of detail so that the remaining thing is comparable across implementations, machines, languages and decades. It works. Merge sort was Θ(nlogn)\Theta(n \log n) in 1945 and it is Θ(nlogn)\Theta(n \log n) now, and the fact survived a complete revolution in what a computer is. Almost nothing else about the analysis of a 1945 algorithm survives that.

What it costs is that the discarded detail is sometimes the answer. The discipline is knowing which situation applies.

Same class, different constantsEvery algorithm here fits n log n on random input. The bar is the fitted constant — comparisons divided by n log n — and the largest is 1.9 times the smallest. That factor is invisible in the notation and is the whole of what distinguishes these algorithms by this measure.comparisons ÷ n log nMerge sort0.855Merge sort with a cutoff0.992Quicksort, random pivot1.018Quicksort, first-element1.082Quicksort, median of three1.117Shellsort1.246Heapsort1.649all of these fit n log n1.9× between best and worst
Fig. 4 Six algorithms that the notation calls identical, and the factor by which they differ. Every one of these is Θ(n log n) on random input. The constants — comparisons divided by n log n — run from 0.84 to 1.61. The classification says these are the same; the measurement says one of them does twice the work of another.

Three sentences that sound like each other

Worth separating, because the difference is where most confusion lives.

Merge sort is Θ(nlogn)\Theta(n \log n).

A claim about how the comparison count grows, in the limit, up to constants. Provable, proved, and true.

Merge sort takes nlognn \log n time.

Not a claim anyone should make. The time depends on the machine, on where the data is, on what else is running. The count and the time are related by a factor that is not remotely constant — the same algorithm on the same machine can vary by an order of magnitude depending on whether its working set fits in cache.

Merge sort is faster than heapsort.

A claim about two implementations on some data on some machine. It might be true. It is not implied by anything in the first sentence, and it needs its own measurement.

The slide from the first to the third happens constantly, usually in a single paragraph, usually without anyone noticing.

The four things the classification leaves out

Given an algorithm and a decision to make, the classification is the first of five useful facts and the least specific.

  1. The class, per input distribution. Which is what the notation supplies.
  2. The constant for the count that dominates the cost. Merge sort’s 0.838 comparisons per nlog2nn \log_2 n against heapsort’s 1.613 — a factor of nearly two that the class hides.
  3. The distribution, not the average. Randomised quicksort’s mean at n = 512 is about 4,900 comparisons and its worst over six hundred runs is meaningfully higher; the tail is what happens on the bad day and an average conceals it entirely.
  4. The memory behaviour. Two algorithms in the same class with the same constant can differ by an order of magnitude in how much of their work reaches main memory.
  5. Where the asymptotic regime starts. The class describes behaviour past some threshold and never says where the threshold is. Below that threshold, the class describes a situation nobody is in.

This site measures all five, and organises itself around the observation that only the first is conventionally reported.

600 runs of quicksort, random pivot at n = 512Every bar is the number of independent random inputs that cost that many comparisons. The mean is 4945; the median is 4908; the worst of 600 runs cost 6,543, which is 1.32 times the mean. The distribution is tight — a relative standard deviation of 6.8% — and skewed to the right, which is the shape that makes "on average" a defensible thing to say about this algorithm and a misleading thing to say about the version that takes the first element as its pivot.mean 494599th 60304,2825,4136,543comparisonsruns600 independent random inputs, n = 512worst run 1.32× the mean
Fig. 5 Point three, drawn. Six hundred runs of randomised quicksort at n = 512, every one a different random input. The mean is a single vertical line through a distribution with a long right tail. “Θ(n log n) on average” summarises this picture in four symbols, and the summary discards the shape.

What this site does instead

The response here is not to abandon the notation. It is to keep it and to measure the things it discards.

Every algorithm declares a class per input kind. The declaration is then fitted against measured counts and granted only if the fit holds — so the class is a claim that can fail, rather than a label. The constant falls out of the fit and is reported. The input kind is always named. The range over which the fit was taken is always named, because a fit over one range and a fit over another can disagree, and on this site they have.

And a second, independent quantity is carried alongside, because the comparison count is not the running time and pretending otherwise is where most of the trouble starts.

Every sort measured on random input, and its claim testedThe exponent fitted to each algorithm's comparison count across n from 32 to 4096, beside the class it claims. The two groups separate cleanly — nothing measures between 1.3 and 1.9 — and every claim on this input is the class that actually fits. The fitted exponent for a linearithmic algorithm sits near 1.2 rather than 1.0 because n log n is not a power law.fitted exponent of the comparison count1.01.52.0Merge sort with a cutoff1.16n log nQuicksort, median of three1.19n log nQuicksort, first-element1.21n log nMerge sort1.21n log nHeapsort1.22n log nQuicksort, random pivot1.24n log nShellsort1.25n log nInsertion sort2.00n^2Selection sort2.01n^2Bubble sort2.01n^2n from 32 to 4096comparisons, counted exactly · random input
Fig. 6 The classification, with its measurements attached. Each bar is a fitted exponent; each label is the class the ratio test granted. This is what asymptotic notation looks like when it is treated as a claim rather than a description — the two families separate cleanly, every declaration survives, and one that did not would stop the build.