Growth Rates and Loop Analysis
With the asymptotic notations in hand, we rank the functions that actually arise in running times — from constant to factorial — proving the orderings between rungs, then read the running time of a loop nest straight off the page. Sequential blocks add, nested loops multiply, index scaling gives logarithms; a worked trace and a tour of cache-aware and galactic algorithms close the lesson.
╌╌╌╌
This builds on Asymptotic Analysis, which defined the , , , , and notations and the limit test for ranking two functions. Here we put that machinery to work: we lay out the growth classes algorithms meet in practice, prove the orderings between them, and then reduce most everyday analysis to counting how many times a loop body runs.
The growth hierarchy
The functions algorithm designers meet most often, in strictly increasing order of growth, are:
| Class | Name | A typical algorithm |
|---|---|---|
| constant | array index, hash lookup (expected) | |
| logarithmic | binary search | |
| linear | scan / find max | |
| linearithmic | merge sort, heapsort | |
| quadratic | insertion sort (worst case) | |
| cubic | naive matrix multiply | |
| exponential | subset enumeration | |
| factorial | brute-force permutations (TSP) |
Each row's growth dwarfs every row above it for large . The practical
dividing line Skiena draws is between polynomial time ( for a constant
), generally considered tractable,
and exponential time, which becomes
hopeless very fast.1 A factorial-time algorithm that handles in
a second needs years at .
Proving the orderings
The table is really a chain of little-o claims: each class is of the one below it. Every link yields to the two moves from the ratio test, L'Hôpital or a substitution, so we prove three representative ones and leave the rest as finger exercises.
This is the sharpest form of logs are cheap
: grows more slowly than
, than , than any polynomial sliver at all. It is the
theorem behind every claim of the form binary search's beats a linear scan.
Chaining such results is painless because little-o is transitive: if and , then (the ratios multiply, and both factors vanish). That single property turns the eight-row table into a totally ordered ladder.
Each class also has a characteristic shape — a picture of how its work fills space, from a single cell up to a tree that explodes:
Those shapes come from how the work is spent — the step size and the work done at each step. The same hierarchy, read as work regimes:
A sketch of the curves makes the separation clear. Even with a generous constant on the slower-growing function, the faster one wins past some crossover :
Reading the cost off loops
Most analysis reduces to counting how many times each line runs. A few rules cover the common cases.
Sequential blocks add; we keep the max. If block costs and is followed by block costing , the total is : the larger term absorbs the smaller.
A simple loop multiplies the body by the iteration count. This nested fragment runs the constant-time body times:
- 1for to do
- 2for to do
- 3body, times
so its cost is .
When the inner bound depends on the outer index, sum a series. If the inner loop runs , the body executes times — still quadratic, because triangular work is half of square work, and the constant vanishes. This is the shape of insertion sort's worst case.
When the loop variable is scaled, take a logarithm. A loop that does until runs about times, since doubles each pass. This is the source of every logarithm in algorithm analysis: repeatedly halving (or doubling) gives steps. Binary search and balanced-tree depth are the canonical examples.
Every loop nest has a shape, and its cost is the size of that shape — usually an area, sometimes just a count of rungs. Lined up in order of growth, the common shapes make the hierarchy concrete: a constant loop touches one cell; a doubling index visits only the powers of two; a flat pass is linear; and an outer loop of passes wrapped around a doubling inner loop fills an grid.
The two costliest shapes in everyday code are the quadratic ones. Two nested loops over the same range fill a square grid — work. Bounding the inner loop by the outer index instead fills only the triangle below the diagonal: half as many iterations, , but the same once the constant is dropped. (A subtler linear case hides nearby: a loop that halves its live problem each pass does total work, so it stays despite touching every prefix.)
A worked trace: three loops, one bound
Rules are easier to trust once you have watched them settle a real fragment. Take a routine that, given an array of numbers, prints the sum of every contiguous block the slow way — recomputing each block's sum from scratch:
- 1for to do
- 2for to do
- 3
- 4for to do
- 5innermost body,
- 6print
Count the innermost body. For a fixed pair it runs times, so the total is
after substituting and using . Let run from down to as climbs, and the outer sum becomes by the hockey-stick identity — that is, .
Pin it down with real numbers at . The block lengths over all pairs are:
| 1 | 2 | 3 | 4 | |
|---|---|---|---|---|
| 1 | 1 | 2 | 3 | 4 |
| 2 | 1 | 2 | 3 | |
| 3 | 1 | 2 | ||
| 4 | 1 |
Summing the entries gives innermost additions, and the closed form checks: . The cubic is real, not an over-count — the third nested loop, whose length grows with the gap , is what lifts the cost from the of the pair-enumeration alone to .
The lesson the trace teaches is where the cost hides. A reader who stops at two visible loops, so
is wrong by a full factor of ; the inner
for $k$ loop supplies that factor. It is also removable: a running prefix-sum
table lets each block sum be read in , collapsing the whole routine to
. Counting first tells you which loop to attack.
The recurrences that arise when a loop is replaced by recursion, a function calling smaller copies of itself, need their own machinery, which is the subject of the next lesson.
When the cost model shifts
The RAM model's flat, unit-cost memory is the assumption that ages worst. On real hardware a cache miss can cost hundreds of times what a hit does, so two algorithms with identical RAM counts can differ by an order of magnitude in wall-clock time. The external-memory (I/O) model of Aggarwal and Vitter (1988) charges for block transfers between a fast memory of size and a slow disk moved in blocks of size , and counts I/Os rather than instructions; scanning items costs transfers, and sorting costs .2 Frigo, Leiserson, Prokop, and Ramachandran (1999) pushed this further with cache-oblivious algorithms, which achieve the optimal transfer count for every block size at once, without ever naming or — recursive layouts like the van Emde Boas tree do this automatically.3 The cost model is itself a modeling choice: pick the one whose expensive operation matches your bottleneck.
A second modern wrinkle sharpens constants don't matter.
They do not matter
asymptotically, but the crossover can sit past every input anyone will run. An
algorithm whose asymptotic bound only beats its rivals for astronomically large
is called galactic. The canonical case is matrix multiplication: the
Coppersmith–Winograd family and its descendants drive the exponent below ,
yet the hidden constants and structure make them useless in practice, where
Strassen's or plain
still win for any feasible matrix.4 The lesson mirrors
small inputs lie
from the previous lesson: an asymptotic verdict is a statement
about the limit, and engineering lives before the limit.
Finally, the polynomial-versus-exponential line the hierarchy draws is the same
line complexity theory draws between P, the problems solvable in polynomial
time, and the harder classes. Cobham (1965) and Edmonds (1965) independently
proposed polynomial time as the formal stand-in for efficient,
which is why the
hierarchy treats as tractable and as hopeless.5 Whether
every problem whose solutions can be checked in polynomial time can also be
solved in polynomial time — the P versus NP question — remains open, and a
great many natural problems (the traveling-salesman tour whose brute force sits at
the bottom of our table among them) are NP-complete: a
polynomial-time algorithm for any one would give a polynomial-time algorithm for
all.6 Growth rates are where practical analysis and the deepest
open question in the field meet.
Takeaways
- Memorize the hierarchy ; each rung is of the next, and little-o's transitivity makes the table a totally ordered ladder.
- The orderings are proved with the ratio test: for every , , and — indeed for every constant , so the factorial outgrows every exponential.
- The polynomial/exponential boundary is the line between tractable and hopeless: a factorial-time routine that handles in a second needs years at .
- Read loop cost by counting: sequential blocks add (keep the max), nested loops multiply the body by the iteration count, an index-dependent inner bound sums a series (a triangle is still ), and a scaled index () gives steps.
- Count before you optimize: the naive all-subarray sums routine hides a in a third loop whose length grows with the gap; a prefix-sum table removes it, dropping the cost to .
- The cost model is a choice. The external-memory and cache-oblivious models count block transfers instead of instructions when memory hierarchy dominates; galactic algorithms win asymptotically but never in practice; and the polynomial/exponential line is the same one P versus NP is drawn on.
Footnotes
- Skiena, §2 — Algorithm Analysis: the polynomial-vs-exponential dividing line between tractable and hopeless running times. ↩
- Aggarwal, A. & Vitter, J. S. (1988).
The input/output complexity of sorting and related problems.
Communications of the ACM 31(9). Introduces the external-memory model and the sorting I/O bound. ↩ - Frigo, M., Leiserson, C. E., Prokop, H. & Ramachandran, S. (1999).
Cache-oblivious algorithms.
Proc. 40th FOCS. Algorithms optimal across all block/cache sizes without naming them. ↩ - Le Gall, F. (2014).
Powers of tensors and fast matrix multiplication.
Proc. ISSAC — the sub- exponent; the termgalactic algorithm
is due to R. J. Lipton and K. Regan for bounds that only help at astronomically large inputs. Strassen, V. (1969).Gaussian elimination is not optimal.
Numerische Mathematik 13. ↩ - Cobham, A. (1965).
The intrinsic computational difficulty of functions.
Proc. Logic, Methodology and Philosophy of Science. Edmonds, J. (1965).Paths, trees, and flowers.
Canadian J. Mathematics 17 — polynomial time as the formal notion ofefficient.
↩ - Cook, S. A. (1971).
The complexity of theorem-proving procedures.
Proc. 3rd STOC; Karp, R. M. (1972).Reducibility among combinatorial problems.
NP-completeness and the P-vs-NP question. ↩
╌╌ END ╌╌