How this site is made

The figure library

Every picture here is generated from code at build time. This page lists the generators, each rendered at its defaults.

No figure on this site is a drawing that was made once and saved. Each one is a function: it takes parameters and returns SVG, so the same generator produces the p4 plate and the p6m plate without either being redrawn.

That is the reason the collection can keep growing without the illustrations drifting apart. A generator is written once, checked once, and every essay that calls it inherits the same line weights, the same colour roles, and the same behaviour in dark mode. There are 25 of them so far.

access-pattern

Where each algorithm looks, and whenEvery array access from one run of each algorithm on 256 random elements: time along the horizontal axis, array index up the vertical. Merge sort makes 49% of its accesses to the next element or the same one; Heapsort makes 15%. That difference is invisible in the comparison count and is most of what the machine feels.Merge sort49% sequential · 7,540 accesses2560Heapsort15% sequential · 14,044 accesses2560time (accesses, left to right) · index (bottom to top)one run each, n = 256every access plotted

amortised-sawtooth

The cost of each of 512 appendsOne spike per append, on a logarithmic vertical axis. Almost every append costs one unit. 9 of them cost the entire current size, because the array had to be copied, and the largest cost 257 — more than half of all the appends put together would suggest. The mean over the whole sequence is 2.00, which is the amortised cost, and it is a true statement about the sequence and a false one about any append in it.110100257amortised 2.000256512append numbercost of that append (log scale)growth factor 2, cost = 1 write + a copy of the array when it resizes9 resizes in 512 appends

asymptotic-error

How wrong the asymptotic formula is on a table you would actually allocateThe vertical axis is the measured probe count minus the formula's, as a fraction of the formula's. At a load factor of 0.5 the formula is within 4.2% at every size. At 0.95 a table of 256 slots comes in 48% below it, and the gap closes as the table grows. The formula is not wrong; it is a limit, and a limit is a claim about a sequence rather than a prediction for any member of it.-50%-40%-30%-20%-10%0%10%20%2561,0244,09616,384α = 0.5α = 0.8α = 0.9α = 0.95table size (slots)measured minus formula, relativemean of 6 fills per pointgreen band: within 5% of the formula

average-converges

The average, as it settles — 500 runs at n = 256The running mean of the comparison count over independent random inputs. After twenty runs it is already within 1.8% of its final value of 2119, and after a hundred it is visually settled. This is why the average-case figures elsewhere on the site are quoted from a few hundred trials rather than from thousands: the estimate stops moving long before the sample gets expensive.202621182211settles at 21191100250500runs included in the averagemean comparisonsn = 256, independent random inputsan average is an estimate with an error

complexity-audit

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

constant-compare

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

crossover

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

decision-tree

Why the floor is log₂(n!): four elements need five comparisonsEach internal node is one comparison and has two outcomes, so a run of the algorithm is a root-to-leaf path and the path's length is that run's comparison count. Every one of the 24 orderings of 4 elements must arrive at its own leaf, or two inputs needing different answers would receive the same one. The deepest tree on the left has 16 leaves. Eight orderings, in red, have nowhere to go — so no comparison sort of four elements can always finish in four comparisons, and the floor is ⌈log₂ 24⌉ = 5.every algorithm that makes at most 4 comparisonsthe 24 orderings of 4 elementsroot16 leaves16 seated · 8 with no leafone comparison per level, two outcomes per comparison⌈log₂(4!)⌉ = 5 comparisons

distance-to-floor

Comparisons used, as a multiple of the floor, n = 256The information-theoretic floor at n = 256 is 1,684 comparisons: a binary decision tree with 256! leaves cannot be shallower than that. The bar is what each algorithm averaged over 16 random inputs, divided by the floor. Merge sort comes within 2% of it; Selection sort uses 19 times as many.the floorMerge sort1.02×Quicksort, first1.23×Quicksort, random1.24×Merge sort + cutoff1.29×Quicksort, median-31.32×Shellsort1.45×Heapsort1.96×Insertion sort9.69×Bubble sort19.26×Selection sort19.38×floor = log₂(256!) = 1,684 comparisonsmean of 16 runs, against a proved bound

floor-curve

The floor, computed two waysThe exact bound log₂(n!) summed term by term, Stirling's closed form laid over it, and n log₂ n above both for comparison. The exact sum and the approximation agree to better than one part in a million across this range. The gap to n log₂ n is n log₂ e, about 1.44n, which is why an algorithm at exactly n log₂ n comparisons is not optimal but is within a factor that shrinks towards 1.1010010³10⁴10010³10⁴10⁵ncomparisonsn log₂ nlog₂(n!)Stirlingthe two routes agree to 5.1e-7 relativeexact sum · Stirling

floor-moves

Two questions, two floors, n = 4096Sorting 4096 elements cannot be done in fewer than 43,250 comparisons; finding one element in a sorted array of 4096 cannot be done in fewer than 13, and binary search's worst case over all 4096 targets is exactly 13. The difference is a factor of 3,327, and it comes entirely from how many outcomes the algorithm must be able to distinguish — 4096! orderings against 4096 + 1 positions. A lower bound is a property of the question, not of any algorithm.comparisons (bar length is log-scaled)sorting, floor43,250sorting, merge sort43,976searching, floor13searching, binary13searching, linear4,096green outline: a proved floor · blue: a measured run3,327× between the two floors

growth-curve

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⁶ncomparisonsInsertionMergeHeapsortQuicksorta power law is a straight line herecomparisons, counted exactly

growth-tradeoff

Choosing a growth factor is choosing where to loseEach point is a growth factor, appending 20,000 elements. Rightwards is more memory left unused when the sequence ends; upwards is more copying per append. Growing by ×1.125 wastes almost nothing and costs 9.89 units per append; growing by ×4 costs 2.09 and leaves 69% of the allocation empty. No point is below and left of every other, which is the definition of a trade-off and the reason real implementations disagree about this number.0%25%50%75%12345×1.125×1.25×1.5×2×3×4capacity left unused at the endamortised cost per append20,000 appends, cost = 1 write + a copy on resizeneither end wins

input-sensitivity

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

order-matters

4,096 accesses, five ordersEvery row performs exactly 4,096 array accesses — the same count an operation-counting analysis would assign them all — and the modelled miss counts differ by a factor of 8. Reading backwards is as cheap as reading forwards, because a cache line is a line whichever end you enter it from. Stepping by 8 elements touches a new line every time and is as expensive as random. Model: fully associative · 32 lines × 8 elements · LRU.cache missesstraight through512100% sequentialbackwards5120% sequentialevery 8th element4,0960% sequentialevery 97th element4,0960% sequentialuniformly random3,8460% sequentialfully associative · 32 lines × 8 elements · LRU8× between best and worst order

partition-step

One partition, with its invariant checked at every stepA Lomuto partition of 16 elements around the pivot 20. Only the steps that moved something are drawn. The invariant — everything left of i is at most the pivot, everything from i+1 to j exceeds it — is checked at every intermediate step by the generator, and the figure does not build if it is ever violated. The pivot finishes at index 6.pivot = 20start74621351061512835363311113820j = 074621351061512835363311113820j = 471021354661512835363311113820j = 1071033546615128353621311113820j = 1271031146615128353621313513820j = 1371031113615128353621313546820j = 1471031113851283536213135466120final swap71031113820283536213135466151green: settled ≤ pivot · orange: under test · purple: the pivotinvariant checked at every step

pivot-rules

What randomising the pivot buys, n = 512For each pivot rule: the range of comparison counts over 400 random inputs (the bar), and the count on an already sorted array (the marker). Taking the first element as pivot costs 130,816 comparisons on sorted input — 26 times its random-input mean, and the quadratic behaviour the algorithm is supposed to avoid. Choosing the pivot at random costs 5,490 on the same input. Randomisation does not make the bad case impossible; it makes it independent of the input, so an adversary who knows the data cannot choose it.first-element pivot130,816 on sortedmedian of three5088 meanrandom pivot4937 meanblue bar: range over 400 random inputs · marker: the already-sorted inputn = 512a bad case that cannot be chosen is a different kind of bad case

probe-count

Probes per insertion against load factor, table of 8,192The line is Knuth's closed form for linear probing, integrated over the fill; the points are a table actually filled and counted. They agree to within 2.3% everywhere. At a load factor of 0.5 an insertion averages 1.49 probes; at 0.9 it averages 5.39. The formula that matters for insertion is the unsuccessful-search one, ½(1 + 1/(1−α)²), and using the successful-search form instead is an error that hides at low load and is glaring at high load.1234560.000.250.500.75formulameasuredload factor αprobes per insertion8,192 slots, mean of 8 fills, seeds stated in lib/structures.jstwo routes agree to 2.3%

quicksort-spread

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

sort-trace

Insertion sort on 24 random elements, six moments from one runEach panel is the array as it actually stood after a particular write, sampled evenly across the 159 writes the run performed. The comparison counter beside each panel is the counter at that instant, so the picture and the numbers come from the same execution. The run finished at 157 comparisons and 0 swaps.after 0 writes0 cmpafter 32 writes32 cmpafter 64 writes63 cmpafter 95 writes94 cmpafter 127 writes125 cmpafter 159 writes157 cmprandom input, seed stated in lib/count.js157 comparisons in this run

tail-growth

The whole range, not the mean — quicksort, random pivotAt each size, the vertical bar spans the best and worst of 200 random inputs, with the mean marked and the information-theoretic floor drawn beneath. The worst run is between 1.18 and 1.38 times the mean and that ratio does not grow with n, so the tail is keeping pace with the average rather than outrunning it. The mean sits about 1.29× the floor throughout.641282565121024204810³10⁴ncomparisonsworst runmeanfloor200 random inputs at each sizerange, not average

tree-shape

The same 63 keys, inserted in two ordersBoth trees hold the keys 0 to 62. On the left they arrived in order, and the tree has height 62 — every node has one child, and a lookup costs a linear scan. On the right the same keys arrived shuffled, giving height 10 against an ideal of 5. Both figures are truncated at depth 16 because the left one does not fit. The logarithmic lookup a binary search tree is chosen for is a property of the insertion order, not of the structure.sorted insertion — height 62shuffled insertion — height 10truncated at depth 1663 keys, identical set, different arrival order62 deep against 10

two-counts

Comparisons and swaps at n = 512Selection sort performs the most comparisons of any algorithm here and among the fewest swaps — it never moves an element it does not have to. Ordering these algorithms by comparisons and ordering them by swaps gives two different orders, which is why the question "how many operations" needs the operation named before it has an answer.comparisonsswapsInsertion sort63,071 / 0Selection sort130,816 / 504Bubble sort129,688 / 62,563Merge sort3,964 / 0Heapsort7,653 / 4,170Quicksort5,049 / 2,380n = 512, random inputcounted in the same run

two-quantities

Comparisons against modelled cache misses, n = 2048One point per algorithm, both axes logarithmic. If the comparison count determined the memory behaviour the points would fall on a line, and they do not: Merge sort and Quicksort, median-3 and Merge + cutoff sit at least two places apart in the two rankings. Cache model: fully associative · 64 lines × 8 elements · LRU. The vertical axis is a modelled miss count, not a time.10⁵10⁶10³10⁴10⁵comparisonscache misses (modelled)Insertion sortSelection sortBubble sortMerge sortHeapsortQuicksort, firstQuicksort, median-3Quicksort, randomShellsortMerge + cutofffully associative · 64 lines × 8 elements · LRUa modelled count, not a time

working-set

The cliff: miss rate against working-set sizeTwenty thousand uniformly random accesses into an array of n elements, replayed through a cache holding 512 elements. Below 512 the miss rate is essentially zero; a factor of eight above it, essentially everything misses. The comparison count of an algorithm says nothing about which side of this cliff it is working on, which is why the two counts are carried separately. Model: fully associative · 64 lines × 8 elements · LRU.0%25%50%75%100%645124,09665,536cache holds 512array size n (elements)miss ratefully associative · 64 lines × 8 elements · LRU20,000 random accesses per point