Stop eyeballing Big-O. Count the operations instead.
Slope measures a JavaScript function's real time complexity by counting the operations it runs, not by timing it. Here is how splicing counters into the AST catches the O(n²) you swore was linear.
You read a function, trace the loops in your head, and declare "that's O(n log n)." You are
usually right, and occasionally you are catastrophically wrong: the .includes() buried in
what looks like a linear scan, the memoization that is not keyed correctly, the fallback that
starts scanning everything once the input crosses a threshold. Big-O is supposed to be a
measurement, but almost nobody measures it. They guess and move on.
Slope replaces the guess with a number. Paste a JS function, pick a few input sizes, and it plots the function's measured operation count against the standard Big-O curves, then names the one it actually matches. It is built for the two moments a wrong guess costs you: proving in an interview that your solution really beats brute force, and reviewing a PR that claims a nested loop over a sorted array is "now O(n)."
Why not just time it?
The obvious approach is a stopwatch: run the function at growing input sizes and watch how long it takes. The problem is that wall-clock time is a liar. JIT warm-up, garbage-collection pauses, and whatever else your machine is doing all bleed into the number, and none of it distinguishes "O(n²) with a tiny constant" from "O(n) with a huge one." Run the same benchmark twice and you get two curves.
So Slope does not time anything. It instruments the code. It parses your pasted function
into an AST with Acorn, splices operation counters directly
into the source (every comparison, arithmetic op, array or object access, and function call gets
a count++ next to it), then runs that rewritten function against each input and reads the
counter. A loop body's cost scales with its iterations because the counter sits inside the loop;
recursive calls accumulate up the stack for the same reason. The count is deterministic, so the
same function draws the exact same curve on your laptop and on a CI runner. Those counts go on a
log-log plot next to reference curves for O(1) through O(2ⁿ), each normalized to your measured
series so what gets compared is the shape of the growth, not an arbitrary magnitude. That
normalization is why "O(n) with a constant of a million" still matches O(n) instead of getting
thrown out for sitting too high on the graph.
The part that is harder than it sounds
Counting operations honestly means respecting control flow. A ternary should count only the arm
that runs. &&, ||, and ?? should count only the operands that actually get evaluated,
because short-circuiting means the right-hand side often never executes. Get that wrong and every
guarded expression inflates its own cost. Slope's counters are branch-aware for exactly this
reason, so the number reflects what ran, not what was written.
There is one deliberate limitation worth being upfront about: Slope counts operations inside the
code you paste, so a native built-in like .sort() or .includes() counts as a single call, not
by its internal cost. If you want a .sort()'s growth measured, write the loop out. This is why
the "secretly O(n²)" sample uses an explicit nested loop rather than a hidden .includes().
My favorite feature falls out of measuring per-run instead of inferring once. Some functions
change complexity class partway up: fast and linear until an input size trips a fallback path,
then quadratic. Slope watches for measured growth diverging from what the early points predicted
and flags the exact size where it happens. One of the built-in samples reports looks O(n),
diverges to O(n²) starting at n=300. A static source reader would never catch that, because
nothing in the text looks wrong.
Try it
Open Slope, click the "looks linear, secretly O(n²)" sample, and press Measure (or ⌘/Ctrl + Enter). Watch the measured dots peel away from the O(n) line. Then paste your own function: it takes a single argument, the generated input, so give a recursive numeric one like Fibonacci the n (number) generator instead of an array. Every run encodes into the URL, so you can drop a result straight into a review comment without a screenshot.
This post is part of the build log: every app my automated factory ships gets written up here, honestly. Browse everything at apps.charliekrug.com. Comments are open below.
Loading comments…