Contents
Paste two functions into the Algorithm Benchmark Runner, press Run, and the page measures both of them on the same seeded array in your own tab. It reports per-implementation timing statistics, a deterministic array-access count, and — optionally — a growth exponent fitted from a ladder of input sizes.
The point is not a leaderboard. It is to make the four ways a casual Date.now() benchmark lies visible at once, and to give you a second, machine-independent signal when the clock is too noisy to trust.
Why one number is not a measurement
A single timed call of a fast function measures the timer, not the code. As MDN’s performance.now() reference documents, the method returns a high-resolution timestamp in milliseconds with up to microsecond precision on a monotonic clock — better than the one-millisecond resolution of Date.now(). That page also records the catch: browsers coarsen the reading to 5 microseconds in a cross-origin isolated document and to 100 microseconds otherwise, so a sub-millisecond function can complete inside one tick of the clock.
The second problem is the engine itself. MDN’s JavaScript execution model reference notes that modern engines “heavily optimize the described semantics,” which is the polite way of saying the thousandth call to your function is not the same code as the first. The runner therefore runs untimed warm-up passes first, then reports a distribution — min, median, p95, max, spread — instead of a single lucky sample.
The third and most embarrassing failure is dead-code elimination. If a function’s return value is never observed, an optimizing compiler is free to conclude the call has no effect and delete it, producing a benchmark that looks impossibly fast. Every call in this runner feeds a numeric result into an XOR accumulator that is read after the loop and written into the results table, so the work has to survive.
The fourth is the garbage collector. Inputs are generated before the clock starts and reused across iterations, so allocation cost is not charged to the algorithm you are comparing.
What the runner actually does
Each lane holds a snippet that must define solution(data) and return a number. The page compiles it with new Function in strict mode, calls it once untimed to catch throws and non-numeric returns, then runs batched samples inside a single performance.now() interval per sample.
Both implementations receive content-identical input from a seeded mulberry32 PRNG, so the comparison survives a reload. The seed, input shape (random, ascending, nearly sorted, reverse sorted), size, iteration count, and warm-up passes are all visible and controllable.
Two signals come out:
- Wall-clock statistics per implementation, with the slower side labelled by ratio, plus an inline chart scaled to the slower lane.
- An array-op count from an untimed pass where the input array is wrapped in a
Proxythat counts indexed reads and writes. This number is deterministic and travels across machines, which is exactly what wall-clock time does not do.
The honesty section, verbatim
Three limits are printed on the page rather than buried in documentation, because they change how you should read every number:
The array-op count only sees indexed access on the array it hands your function. A function that copies the array first — slice, spread, structuredClone — goes blind after the copy, so a low count is not proof of low work. In the default sorting example, insertion sort shows 2,000 counted reads for a 2,000-element input, while its real comparison count is far higher.
The growth exponent is a least-squares fit of log(ms) against log(n) over the ladder sizes actually measured. In a run on 2,000 random integers, the page fitted 1.86 for insertion sort and 1.09 for merge sort — consistent with quadratic versus linearithmic behaviour at those sizes, and still an estimate, not a proof. A fit needs at least three distinct sizes with real spread; the page says so instead of printing a number when the ladder is too narrow.
Memory is reported only where the browser exposes it. performance.memory is a coarse, Chromium-only, bucketed heap reading; when it is unavailable the page prints that instead of a fabricated figure. Heap deltas around a loop are an order-of-magnitude signal at best.
When it will still lie to you
The tab cannot pin a CPU, silence other tabs, or control thermals, so only comparisons measured in the same tab during the same run are meaningful. A p95 that includes a collection pause is honest noise, not a defect. And the page cannot interrupt a non-terminating function: if your snippet loops forever, the tab hangs — that limit is printed above the Run button rather than discovered the hard way.
What the tool does give you is a reproducible setup: a seed you can copy into a report, a checksum that tells you whether the two implementations even agree, and a Markdown summary you can paste into a PR description.
The runner is the first of the planned Benchmarking & Performance tools to ship, and the first on this site that measures runtime behaviour rather than reading source text.



