Growth Rates and the Asymptotic Hierarchy

Imagine two programs solving the same problem. One takes 100·log₂n steps, the other takes n steps. Which is faster? For n = 1000, the first takes about 1000 steps. The second also takes 1000 steps — a tie. But for n = 10,000,000, the first takes about 2300 steps. The second takes 10,000,000 steps. The constant 100 stopped mattering. Only the shape of the growth curve did. This is the central fact that makes algorithm analysis possible at all. As inputs get large, functions separate into a strict hierarchy of growth rates. That hierarchy — not the messy constants in front — determines which algorithm wins. This matters far beyond homework. A database might choose between a linear scan and a binary search. A compiler might decide whether an optimization pass is safe on million-line codebases. A systems engineer might estimate whether a sorting step will dominate a pipeline. All of these decisions reduce to one question. Where does a function's growth rate sit in a small, well-understood ladder of families? The ladder runs: constant, logarithmic, linear, linearithmic, polynomial, and exponential. Engineers memorize this ladder because it lets them reason about scalability without running the code. This article assumes familiarity with Big-O Notation and its relatives Omega and Theta. Those describe how we bound a function's growth. Here the question is different and complementary. Given two functions, which one grows faster? How do we prove it rigorously? We build the standard hierarchy of growth-rate classes. We show how to compare any two functions using limits of ratios. We connect this to real running-time functions that show up when analyzing loops and recursive algorithms. By the end, you will be able to place an unfamiliar function into the hierarchy. You will be able to prove an ordering between two functions using the limit test. You will also recognize the practical thresholds that make these classes matter. For example: around what problem size does an n log n algorithm overtake a quadratic one? These thresholds matter in engineering practice, not just in theory.

Key takeaways
  • The asymptotic hierarchy orders function families by growth rate: 1 ≺ log n ≺ n ≺ n log n ≺ n² ≺ 2ⁿ ≺ n!, with polynomials of higher degree slotting in between n and 2ⁿ.
  • "f grows slower than g" is made precise by the limit test: compute lim(n→∞) f(n)/g(n); it being 0, a positive constant, or infinity tells you f = o(g), f = Θ(g), or f = ω(g) respectively.
  • The hierarchy is transitive and total on the standard families, so you rarely need to compare two arbitrary functions from scratch — you place each in its class and read off the order.
  • Constants and lower-order terms are eventually irrelevant: any exponential beats any polynomial, and any polynomial beats any logarithmic power, no matter the constant factors involved.
  • Crossover points are real and matter in practice — an O(n²) algorithm can outperform an O(n log n) one for small n, which is why hybrid algorithms (e.g., introsort, Timsort) switch strategies below a threshold.
  • This hierarchy is the vocabulary used to analyze loops, recurrences, and the Master Theorem — understanding it precisely is a prerequisite for everything that follows in algorithm analysis.
The core insight

Growth rates form a strict, provable ordering — constant < logarithmic < linear < linearithmic < polynomial < exponential — and the limit of the ratio of two functions is the exact tool that proves where any new function fits.

The idea

A growth rate describes how the value of a function scales as its input grows without bound, ignoring additive and multiplicative constants that don't change with the input. Two functions belong to the same growth class if each is bounded by a constant multiple of the other for all sufficiently large inputs — this is precisely the relation Θ from asymptotic notation. The asymptotic hierarchy is the sequence of standard classes ordered by growth rate: constant, logarithmic, linear, linearithmic (n log n), polynomial, exponential, and factorial. Each class strictly dominates the ones before it, meaning any function in a later class eventually exceeds any function in an earlier class, no matter what constants are attached. To compare two functions f(n) and g(n) rigorously rather than by intuition, we use the limit test. Consider ``` L = lim(n→∞) f(n) / g(n) ```

  • If L = 0, then f grows strictly slower than g, written f = o(g) (little-o).
  • If L is a finite positive constant, then f and g grow at the same rate, f = Θ(g).
  • If L = ∞ (or the ratio is unbounded), then f grows strictly faster than g, written f = ω(g).
  • If the limit does not exist and is not infinite (rare for the functions we care about, but possible for oscillating functions), the ratio test is inconclusive and a different technique — such as squeezing between two known bounds — is needed.

This gives a precise, mechanical way to settle "which grows faster" questions instead of eyeballing graphs. It is the analytic backbone behind the informal ladder every programmer learns to recite. The key insight that makes the hierarchy useful in practice is domination: within each class, constants and lower-order additive terms are irrelevant to the classification. A function like 3n² + 1000n + 50 is Θ(n²) — the n² term dominates, and everything else is asymptotically noise. Understanding why this domination happens (via the limit test, or via direct bounding arguments) is what separates confident asymptotic reasoning from pattern memorization.

How it works

The mechanism for building and using the hierarchy has three parts: (1) establish a canonical list of reference functions, (2) prove the pairwise ordering between adjacent classes using the limit test or elementary bounding arguments, and (3) classify an unfamiliar function by comparing it against the closest reference functions.

Step 1 — the reference ladder. The canonical growth classes, from slowest to fastest, together with typical algorithmic examples:

ClassTypical functionExample algorithm
Constant1array index lookup
Logarithmiclog nbinary search
Linearnsingle scan of an array
Linearithmicn log nmergesort, heapsort
Quadraticn²insertion sort, bubble sort
Cubicn³naive matrix multiplication
Polynomial (general)n^k, k > 1dynamic programming over pairs/triples
Exponential2ⁿ, cⁿsubset enumeration, brute-force SAT
Factorialn!brute-force TSP, permutation search

Step 2 — proving adjacent orderings. Each strict inequality f ≺ g (f grows strictly slower than g) in the ladder above can be proved with the limit test or with a direct algebraic bound. Three representative proofs: log n ≺ n. Let f(n) = log n, g(n) = n. By L'Hôpital's rule (valid since both go to infinity), ``` lim(n→∞) log n / n = lim(n→∞) (1/n) / 1 = 0 ``` so log n = o(n). n log n ≺ n². Write the ratio as ``` n log n / n² = log n / n ``` which tends to 0 by the previous result. Hence n log n = o(n²). n^k ≺ 2ⁿ for any fixed k. This is the most important inequality separating "tractable" from "intractable" algorithms. Using the substitution n = 2^m is instructive, but the cleanest argument is via the ratio of consecutive terms or via L'Hôpital applied k times: ``` lim(n→∞) n^k / 2ⁿ = lim k·n^(k-1) / (2ⁿ ln 2) = ... = lim k! / (2ⁿ (ln 2)^k) = 0 ``` Each application of L'Hôpital reduces the polynomial's degree by one while the denominator remains an exponential (up to constants), and after k applications the numerator is a constant while the denominator still diverges. Hence n^k = o(2ⁿ) for every fixed k — no polynomial, however high its degree, ever catches an exponential.

Step 3 — classifying an arbitrary function. Given an unfamiliar running-time expression, the practical algorithm is: ``` CLASSIFY(f): // f is a running-time expression in n simplify f by discarding lower-order additive terms and constant factors identify the dominant term T(n) — the summand that grows fastest compare T(n) against the reference ladder using the limit test: for each pair of adjacent reference functions (r_i, r_{i+1}): compute L = lim(n→∞) T(n) / r_i(n) compute L' = lim(n→∞) T(n) / r_{i+1}(n) if L is a positive finite constant: T is Θ(r_i); return class i if L = ∞ and L' is finite and positive: T is Θ(r_{i+1}); return class i+1 report T's class and hence f = Θ(T) ``` This is essentially binary search over the ladder using the limit test as the comparison oracle — a nice small illustration of the very ideas (search, comparison) that the hierarchy is used to analyze. ```pseudocode function COMPARE(f, g): // Returns "slower", "same", "faster" describing f relative to g L := limit as n -> infinity of f(n) / g(n) if L == 0: return "f grows strictly slower than g" // f = o(g) else if L is finite and L > 0: return "f and g grow at the same rate" // f = Theta(g) else if L == infinity: return "f grows strictly faster than g" // f = omega(g) else: return "inconclusive: limit does not settle to 0, constant, or infinity" ``` The pseudocode is intentionally a "meta-algorithm": in practice the limit is computed by hand using L'Hôpital's rule, algebraic manipulation, or known growth facts (logs grow slower than any positive power, polynomials grow slower than any exponential with base > 1, exponentials grow slower than factorial).

Figure
The asymptotic hierarchy, plotted as a ladder of dominance
Growth-rate hierarchy (slowest → fastest, left to right) n (input size) → time 1 log n n n log n n² 2ⁿ Ordering: 1 ≺ log n ≺ n ≺ n log n ≺ n² ≺ 2ⁿ

Complexity & correctness

What is being claimed, precisely. The hierarchy asserts a strict total order (up to Θ-equivalence) on the reference classes: for any two classes A ≺ B in the ladder, every function in class A is o(every function in class B). This is not an empirical observation about typical algorithms — it is a theorem about real-valued functions, provable directly from the definitions of limits and of Big-O/little-o.

Correctness of the limit test. The limit test is correct because it is a restatement of the little-o and Θ definitions in analytic form. Recall f = O(g) means there exist constants c > 0 and n₀ such that f(n) ≤ c·g(n) for all n ≥ n₀. If lim f(n)/g(n) = L for finite L ≥ 0, then for any ε > 0 there is n₀ such that for n ≥ n₀, ``` L - ε ≤ f(n)/g(n) ≤ L + ε ``` Choosing c = L + 1 (say, with ε = 1) directly gives f(n) ≤ c·g(n) for n ≥ n₀, which is exactly the Big-O condition. If additionally L > 0, the same argument with the reciprocal ratio gives g = O(f), so f = Θ(g). If L = 0, no positive lower bound survives, giving f = o(g) (strictly weaker than Θ but a stronger statement than O). If L = ∞, swapping the roles shows g = o(f), i.e., f = ω(g). This is why the limit test is not a heuristic — it is a direct algebraic derivation of the asymptotic relation from the calculus definition of a limit.

Transitivity gives the ladder for free. Once individual adjacent comparisons are proved (log n ≺ n, n ≺ n log n, n log n ≺ n², n^k ≺ 2ⁿ, 2ⁿ ≺ n!), transitivity of o(·) — if f = o(g) and g = o(h) then f = o(h), which follows by multiplying the two ratios, since the limit of a product of two sequences each tending to 0 (or one tending to 0 and one bounded) still tends to 0 — extends every adjacent inequality to a full ordering across the entire ladder. This is why you never need to directly compare, say, log n against n³: you chain the known adjacent facts.

Honesty about the limits of the limit test. Three caveats matter: 1. Not all pairs of functions are comparable by a clean limit. Functions that oscillate (e.g., f(n) = n(2 + sin n)) can make the ratio f(n)/g(n) fail to converge even though f and g are both "reasonably behaved." In algorithm analysis this is rare because running-time functions are almost always monotonic and smooth, but it is not a theorem that the limit always exists. 2. The hierarchy classifies growth rate, not actual wall-clock speed. A Θ(n log n) algorithm with a huge hidden constant can be slower than a Θ(n²) algorithm for all inputs one will ever run in practice. Asymptotic dominance is a statement about behavior as n → ∞, and "∞" can be astronomically larger than any n encountered in an application. This is precisely why practical libraries hybridize (see below). 3. Polynomial exponents must be compared carefully. n^1.5 sits strictly between n and n², and no amount of eyeballing substitutes for actually running the limit test: lim n^1.5/n² = lim 1/√n = 0, so n^1.5 = o(n²), confirming intuition, but the same style of argument is needed whenever exponents are irrational or when logarithmic factors are mixed in (e.g., comparing n log² n against n^1.1 requires the fact that any log power is dominated by any positive polynomial power, itself provable by L'Hôpital or substitution n = eᵐ).

Complexity of the classification procedure itself. Determining which class a given closed-form expression belongs to is, in the well-behaved cases seen in algorithm analysis (sums of monomials, logarithms, and exponentials), a constant number of limit computations — essentially O(1) symbolic steps once the dominant term is identified, since the dominant term of a sum is found by comparing at most a handful of summands pairwise. This is not a claim about arbitrary computable functions (in general, comparing the growth of two functions is undecidable), but for the polynomial/log/exponential expressions that arise from analyzing loops and recursions, the classification is mechanical and fast.

Worked example

Consider four candidate running-time functions produced by analyzing four different algorithms on an input of size n, and the task of ranking them: ``` f1(n) = 500 n + 2000 f2(n) = 3 n log2(n) f3(n) = 2 n^2 - 100 n f4(n) = 0.001 · 2^n ```

Step 1 — strip constants and lower-order terms.

  • f1(n): dominant term is 500n → class linear, Θ(n).
  • f2(n): dominant term is 3n log n → class linearithmic, Θ(n log n).
  • f3(n): dominant term is 2n² (the −100n is lower order) → class quadratic, Θ(n²).
  • f4(n): dominant term is 0.001·2ⁿ → class exponential, Θ(2ⁿ), since a positive constant factor never changes the class.

Step 2 — verify f1 ≺ f2 with the limit test, since it is not immediately obvious for small n whether the constant 500 changes anything: ``` lim(n→∞) f1(n)/f2(n) = lim (500n + 2000) / (3n log2 n) = lim (500 + 2000/n) / (3 log2 n) = 500 / ∞ = 0 ``` So f1 = o(f2): despite the large constant 500 in front of f1, the logarithmic factor in f2 eventually wins, but only after a large crossover point.

Step 3 — find the crossover numerically. Solve 500n + 2000 ≈ 3n log₂ n:

nf1(n) = 500n+2000f2(n) = 3n·log₂n
107,000100
1,000502,00029,900
100,00050,002,0004,983,000
10,000,0005,000,002,000706,000,000
10^15≈ 5×10^17≈ 1.5×10^17
10^18≈ 5×10^20≈ 1.79×10^20

Even at n = 10^18, f1 is still larger — the crossover for these particular constants happens at an astronomically large n. This table is a deliberately non-trivial (asymmetric) illustration of a crucial, often-missed lesson: the asymptotic ordering is correct in the limit, but the constant 500 versus 3 makes the crossover point practically irrelevant for this pair. An engineer choosing between these two algorithms for realistic input sizes (say n ≤ 10^9) should pick f1's algorithm, even though f2 is the asymptotically superior one — a direct illustration of why "asymptotically better" and "faster in practice" are not synonyms.

Step 4 — rank all four. Using the proven ladder n ≺ n log n ≺ n² ≺ 2ⁿ together with the identified dominant terms: ``` f1 (Θ(n)) ≺ f2 (Θ(n log n)) ≺ f3 (Θ(n²)) ≺ f4 (Θ(2ⁿ)) ``` for sufficiently large n — with the caveat from Step 3 that "sufficiently large" can be far outside any realistic input range for some pairs, and much smaller for others (f3 overtakes f2 for n around a few hundred, and f4 overtakes f3 for n around 20–25, both entirely realistic sizes to hit in practice).

Common pitfalls & traps

PitfallWhy it happensFix
Comparing constants instead of shapesFor small, concrete n, a "worse" class can look faster because of large hidden constantsAlways reason about the limit as n → ∞, and separately sanity-check realistic n ranges
Forgetting the base of a logarithm doesn't matter for classificationlog₂n and log₁₀n differ by a constant factor (change of base), so both are Θ(log n)State the class using base-independent Θ, only pin down the base if computing an exact constant
Treating n log n as "basically linear"Programmers sometimes round n log n down to O(n) informallyKeep n log n as its own class; it strictly dominates n by the limit test (log n → ∞)
Assuming all polynomials of the same degree are equivalent in practice3n² and 3000n² are both Θ(n²) but differ by a factor of 1000 in real running timeUse Θ for classification, but keep leading constants when estimating wall-clock behavior
Misapplying the limit test to non-monotonic or oscillating functionsRatio may not converge, so "the limit is 0/∞" reasoning silently breaksCheck monotonicity/boundedness first, or bound the function between two comparable ones (squeeze)
Sliding a polynomial across an exponential boundaryMistaking n^100 as "eventually slower than 1.01ⁿ" is true, but for enormous n, easy to misjudge when it startsProve it via L'Hôpital or n^k = o(cⁿ) for any c > 1, don't eyeball a graph over a small range

Pioneers & history

The asymptotic hierarchy as taught today crystallizes ideas from analytic number theory (comparing the growth of arithmetic functions) with the needs of computational complexity theory, which needed a rigorous vocabulary for "how does cost scale."

Origins · 1 minPaul Bachmann and how this was discovered
PB
Paul Bachmann
1837–1920 · German number theorist who introduced O-notation in 1894 to compare the growth of number-theoretic functions, laying the notational foundation later adopted wholesale by computer science

DE
Donald Ervin Knuth
born 1938 · American computer scientist whose 1976 paper "Big Omicron and Big Omega and Big Theta" standardized O, Ω, Θ notation for algorithm analysis and popularized the growth-rate hierarchy as the working vocabulary of the field

History
1894
— Paul Bachmann introduces O-notation to compare growth of functions in analytic number theory.
1909
— Edmund Landau adopts and popularizes o-notation (little-o), giving the pair now called Landau notation.
1965
— Juris Hartmanis and Richard Stearns formalize time complexity classes, tying growth rates to computational tractability.
1971
— Stephen Cook's theorem on NP-completeness sharpens why the polynomial-versus-exponential boundary in the hierarchy is the central dividing line of complexity theory.
1976
— Donald Knuth's paper standardizes Θ notation and the modern convention for stating tight growth-rate classes in algorithm analysis.

Where this matters

Every time a system designer chooses between a hash table (expected Θ(1) lookup) and a sorted array with binary search (Θ(log n) lookup), or between a naive nested loop and a divide-and-conquer algorithm, they are implicitly reasoning inside this hierarchy. Database query planners estimate whether a join should use a nested-loop scan (polynomial) versus a sort-merge or hash join (linearithmic or linear), directly using growth-rate reasoning to pick execution plans that scale to millions of rows. Compiler and language-runtime engineers use the hierarchy to bound the cost of type inference, garbage collection sweeps, and register allocation, since these subsystems run on every compiled program and even a jump from linear to quadratic behavior can make a compiler unusable on large codebases. Standard library sorting routines are a textbook case of taking the hierarchy seriously while respecting its practical caveats: Timsort (Python, Java) and introsort (C++'s std::sort) are Θ(n log n) in the worst case, matching the proven lower bound for comparison sorts, yet they fall back to insertion sort — an asymptotically worse Θ(n²) algorithm — for small subarrays, because the crossover point analysis (as in the worked example above) shows insertion sort wins below a few dozen elements. Cryptography relies on the polynomial-versus-exponential boundary directly: RSA's security rests on integer factorization having no known polynomial-time algorithm, meaning the best attacks live in the exponential (or sub-exponential) region of the hierarchy while legitimate encryption/decryption operations are polynomial — an asymmetry the whole field depends on. Machine learning systems reason about the hierarchy when choosing between O(n) streaming algorithms and O(n²) kernel methods for training set sizes in the billions, where the gap between linear and quadratic is the difference between feasible and impossible.

Ask the tutor: How do I find the exact crossover point where one growth class overtakes another for specific constants?

Why does the base of a logarithm not matter when classifying growth rate?

Any two logarithms are related by the change-of-base formula: log_b(n) = log_a(n) / log_a(b), so log_b(n) and log_a(n) differ only by the constant factor 1/log_a(b), which does not depend on n. Since Θ-classification explicitly allows multiplication by positive constants, log₂n, log₁₀n, and ln n all land in the same growth class, written simply Θ(log n) without specifying a base. This is why algorithm analysis rarely bothers to state which logarithm is meant — binary search's Θ(log n) bound is the same class whether you compute it with base 2 or base e. The base only matters if you want the exact leading constant for a concrete estimate, not for the asymptotic classification itself.

How is the limit test different from just checking Big-O directly from the definition?

The Big-O definition asks you to find explicit constants c and n₀ satisfying f(n) ≤ c·g(n) for n ≥ n₀, which can require some algebraic cleverness to construct. The limit test instead computes lim f(n)/g(n) using standard calculus tools (L'Hôpital's rule, algebraic simplification, known limits), and this computed value directly implies the appropriate constants exist. In practice the limit test is usually faster and less error-prone for polynomial, logarithmic, and exponential functions, because calculus techniques for evaluating limits are more mechanical than guessing witnesses for an existential statement. The two approaches are equivalent in what they prove; the limit test is simply a more efficient proof technique for the function families that show up in algorithm analysis.

Is n^1.5 closer to n or to n²?

Neither comparison is really about "closeness" in a numeric sense — it's about which class n^1.5 belongs to relative to the two neighbors, and the answer is that n^1.5 is strictly between them: n = o(n^1.5) and n^1.5 = o(n²). You can verify both with the limit test: lim n/n^1.5 = lim n^{-0.5} = 0, and lim n^1.5/n² = lim n^{-0.5} = 0 as well. So n^1.5 sits properly in between, forming its own growth class, distinct from both linear and quadratic. Functions like this arise naturally, for example in some divide-and-conquer recurrences analyzed via the Master Theorem, where the recursion produces a running time like Θ(n^1.585) (Karatsuba's multiplication algorithm).

Does a lower asymptotic class always mean a better algorithm to use?

Not necessarily, and this is one of the most important practical lessons of asymptotic analysis. Asymptotic classification describes behavior as input size grows without bound, but real inputs are finite, and the constants hidden inside the O or Θ can differ by orders of magnitude between two algorithms. A Θ(n log n) algorithm with heavy constant overhead from cache-unfriendly memory access patterns or expensive per-comparison work can lose to a Θ(n²) algorithm for every input size that actually occurs in an application. This is exactly why real-world implementations benchmark at realistic scales and often hybridize, as with introsort's fallback to insertion sort for small subarrays. Asymptotic analysis tells you what must eventually happen, not what happens on your actual workload.

How does this hierarchy connect to analyzing loops and recurrences?

When you count operations in a piece of code — for example, nested loops or a recursive divide-and-conquer procedure — you typically end up with a closed-form or recursive expression for the running time. The technique of Analyzing Loops by Counting Operations produces exactly the kind of polynomial-plus-logarithm expressions discussed here, which then need to be classified using precisely this hierarchy to state a clean Big-O or Θ bound. Similarly, the Master Theorem solves recurrences of the form T(n) = aT(n/b) + f(n) by comparing f(n) against n^(log_b a) using ideas identical to the limit test described above. In both cases, the hierarchy is the vocabulary that turns "count the steps" into "state the complexity class."

Can a function fail to belong to any of the standard classes?

Yes — the standard ladder (constant, log, linear, n log n, polynomial, exponential, factorial) is a convenient, commonly occurring set of reference points, not an exhaustive classification of all functions. Functions like n^(log n) (which grows faster than any fixed polynomial but slower than any exponential cⁿ with c > 1) or the inverse Ackermann function α(n) (which grows slower than any iterated logarithm) fall between the named classes or below all of them. These exotic functions do appear in algorithm analysis — n^(log n) shows up in some graph algorithms, and α(n) appears in the amortized analysis of union-find — and are typically described directly rather than forced into one of the six named classes. The named hierarchy covers the overwhelming majority of running times encountered in introductory and intermediate algorithm design.

What's the difference between saying f = O(g) and saying f grows strictly slower than g?

f = O(g) is a non-strict upper bound: it permits f and g to grow at the same rate (f = Θ(g) implies f = O(g) too), so O(g) alone doesn't tell you whether f is strictly slower or tied with g. "Strictly slower" corresponds to the little-o relation, f = o(g), which is exactly what the limit test detects when the limit of f(n)/g(n) equals 0. This distinction matters when stating the asymptotic hierarchy precisely: saying log n ≺ n means log n = o(n), a strictly stronger and more informative statement than merely log n = O(n). Conflating O and o is a common source of imprecise claims, since O(g) alone is compatible with f and g being asymptotically identical.

Before this

Where this leads