python/volume-3/chapter-47ch 47 / 64 · 7 min

47How to think like the 1%

In Chapter 46 we reached for randomness when an exact answer cost too much. That was the last technique in a volume full of them. This chapter teaches no new algorithm. It teaches the thing underneath all of them: the operating system for an algorithmic mind, the handful of reflexes that turn a whole volume of techniques into one way of seeing. Here's the plan. We take the moves you already own and watch them collapse into habits you run without thinking: derive the cost instead of memorizing it, read a problem's signature and match it to the right tool, then run one relentless loop on anything too slow. The whole way through, we keep asking the one thing that actually matters — sat in front of a problem you've never seen, what do you actually do first? The gap between code that melts under load and code that scales to a billion users isn't a bigger memory or a faster brain. It's that loop, run so many times it became instinct. By the end you'll know exactly what to ask, what to reach for, and how to tell whether your fix actually worked — the whole volume, folded into a way of thinking you can carry anywhere.

Derive it — never memorize it The toolkit is a flowchart, not a menu The loop that cracks a problem you've never seen Respect the constant — machine sympathy
scroll to begin

01Derive it — never memorize it

Let's start with the single worst way to learn this material: memorizing a table — sorting is n log n, hashing is O(1), and so on down the page. Watch what that does to you. Memorized facts rot. And they teach you nothing about the problem in front of you that isn't already sitting in that table. The 1% do the exact opposite. They derive the complexity on the spot, by counting the work as the input grows — the precise move you built back in the growth-zoo chapter. The growth zoo was never a list to recite; it's a lens you look through. And there's a shortcut that makes the derivation almost mechanical: count the operations at some size n, then again at 2n, and read the ratio.

derive.pypython
def quad(n):                 # a full nested loop
    ops = 0
    for i in range(n):
        for j in range(n):
            ops += 1
    return ops

for n in (1000, 2000, 4000, 8000):
    print(n, quad(n))
# 1000 1000000   |  double n -> 4x the work
# 2000 4000000   |  ratio 4  ->  the exponent is 2
# 4000 16000000  |  ratio 4  ->  O(n squared)
# 8000 64000000  |  ratio 4

Read it line by line. quad just tallies how many times the inner body runs. We call it at four sizes, each double the last, and print the count. The counts we got were 1,000,000, then 4,000,000, then 16,000,000, then 64,000,000. Every time we doubled n, the work went up four-fold. That ratio is the answer. Doubling the input and getting 4× the work means cost ∝ , because 4 = 2². You never had to remember that nested loops are quadratic — you watched the exponent fall out of the ratio. Run the same probe on six different shapes and the ratios sort themselves into the whole zoo:

double n → measure how the work multiplies → read the class ratio ≈ 1O(1) — work never grows ratio ≈ 1.1O(log n) — one extra step per doubling ratio ≈ 2O(n) — work tracks the input ratio ≈ 2.2O(n log n) — a hair above linear ratio ≈ 4O(n²) = 2²; ratio 8 → O(n³) = 2³
Fig — The doubling-ratio decoder. The multiplier when you double n is 2 raised to the exponent — so the ratio hands you the exponent directly. No table required.
InteractiveDerive it — slide through snippets, read the exponent off the ratio
snippet: full nested for i in range(n): for j in range(n): ops += 1 bar height = log-scaled op count at n = 1k · 2k · 4k · 8k work multiplier when n doubles ×4.0 → derived class O(n²) 4 = 2² → exponent 2
O(n²)
You never recite a rule. You double the input, watch the multiplier, and the exponent is just its logarithm, base 2.
The deeper cut — why the ratio is exactly 2 to the power of the exponent
If the true cost is c·n^k for some constant c and exponent k, then doubling the input gives c·(2n)^k = c·2^k·n^k, so the new cost divided by the old is exactly 2^k — the constant c cancels, which is why this trick sees through machine-dependent constants (the Big-O chapter's whole reason for dropping them). Take the base-2 logarithm of the measured ratio and you get k back: log₂(4)=2, log₂(8)=3. Logarithmic cost is the interesting exception: doubling n adds a fixed number of steps rather than multiplying, so the ratio drifts toward 1 from above (10→11→12→13 in our run) instead of settling on a clean power. That drift-to-one is the fingerprint of a log.
InteractiveThe wall-clock gulf — same input, five curves, one clock
the same n · five curves · one clock machine: 1 billion ops / second n = 1.0M items O(log n) O(n) O(n log n) O(n²) O(2ⁿ) bars log-scaled: length ∝ log₁₀(operations) — 20 orders of magnitude on one screen THE GULF AT THIS n O(n²): 18.3 min O(n log n): 21.0 ms · O(log n): 20 ns Same 1.0M items — O(n log n) finishes in 21.0 ms, O(n²) in 18.3 min.
n = 1.0M — n² is 52,429× slower
Big-O isn't academic bookkeeping — it's a clock. Drag n toward a billion and watch O(n²) fall off the edge of a human lifetime while O(n log n) stays a coffee break — and O(2ⁿ) passes the age of the universe before n even reaches 128. Same input; the curve you reached for is the whole difference. That is why this chapter exists.

So you can name any single function's growth on sight. But real work isn't "name this loop" — it's "I have a problem; which tool do I even reach for?" That needs a different reflex →

02The toolkit is a flowchart, not a menu

Beginners keep their algorithms in a list and scan it hopefully, top to bottom. Experts keep them in a decision tree keyed on the signature of the problem — a handful of tell-tale features that each point straight at a technique. Learn the triggers, not just the tools. Here is the whole volume compressed into that tree. Read the problem, spot the signature, follow the arrow.

a new problem — ask… sorted / monotonic answer?→ Binary search · ch12 "have I seen this before?"→ Hashing · ch6 overlapping subproblems?→ Dynamic prog. · ch9 pairs / subarray / in-place?→ Two pointers · ch7 choices, build & abandon?→ Backtracking · ch10 network / dependencies?→ Graphs · ch13 safe best-local move?→ Greedy (prove it!) · ch8 no exact answer / too big?→ Randomize · ch15 None of these? → brute force it (ch4), then hunt the wasted work and re-run the tree.
Fig — The pattern toolkit as a decision tree. You don't search the menu; you match the signature. Each arrow is a chapter of this volume.
InteractiveWhich technique? Flip the switches that describe your problem
flip a switch below to light its technique
This is the flowchart, made of switches. Real problems trip several at once — that's the moment to ask which signature is strongest.
↺ The thing people get backwards
People think mastery means memorizing more algorithms. It's the reverse: mastery is memorizing more triggers — the signatures that tell you which of a small set of algorithms applies. There are only a dozen or so core techniques in this whole volume. The 1% don't know more of them than you; they recognize the situation that calls for one faster, because they've filed each algorithm under "when do I reach for this?" instead of only "how does this work?". Index your tools by their trigger, not their mechanism.
Where you meet this
Every system you touch is this tree, already followed. Your phone's contacts open instantly because someone saw "look this up by name" and reached for hashing (ch37). GPS reroutes around traffic because someone saw "shortest path in a network" and reached for a graph algorithm — Dijkstra (ch44). git bisect finds the commit that broke the build in a handful of steps because someone saw "monotonic: good before, broken after" and reached for binary search (ch43). git diff, spell-check, and DNA alignment all run edit-distance DP (ch40) because someone saw "overlapping subproblems." The signature was the whole insight; the code was downstream.

The tree tells you what to reach for. But what do you do before you know the answer — when nothing in the tree obviously fits? You run the loop →

03The loop that cracks a problem you've never seen

Here is the method that survives contact with the unknown: the four-beat cycle from the brute-force chapter, now as your default operating procedure. One: solve it the dumbest way that works — brute force, no cleverness, just a correct answer. Two: measure the baseline — count its operations, name its Big-O. Three: ask the one question that unlocks everything — where is the wasted work? Look for the thing you compute again and again, the search you redo, the pair you re-examine. Four: kill that waste with a technique from the tree, then re-check the complexity to confirm the curve actually dropped. Then loop.

1 · bruteforce 2 · measurebaseline 3 · where'sthe waste? 4 · apply +re-check still wasteful? loop again — each turn drops the curve
Fig — The loop is the whole game. It doesn't require you to know the answer up front — it manufactures the answer, one removed inefficiency at a time.

Watch it turn on a problem you've already met. Two-sum: find two numbers in a list that add to a target. Beat one, brute force: check every pair. That's two nested loops, and the doubling probe from earlier would report ratio 4, so it's O(n²). Beat three, the question: what work repeats? For each number x you re-scan the whole list looking for target − x. You keep asking "is this value present?" over and over. Beat four: that exact phrase — "have I seen the value I need?" — is a trigger in the tree, and it points at hashing (ch37). Store each number in a set as you pass it, and the lookup becomes O(1). The whole thing collapses to O(n). One pass through the loop turned a quadratic into a linear. You didn't need to be clever — you needed to be systematic. (Step it live in trace T30.)

Solve, then optimize — in that order, always
The brute-force answer is never wasted. It pins down what "correct" even means, it gives you the baseline curve to beat, and often it ships — because at your real n it may already be fast enough (the space & memory-hierarchy chapter: respect the constant, and never optimize a curve that isn't your bottleneck). Premature optimization skips beats one and two and lands you with fast code that solves the wrong problem.

The loop drops your Big-O. But two programs with the same Big-O can still finish minutes apart. To win there, you have to think about the metal →

04Respect the constant — machine sympathy

Big-O is necessary, not sufficient. It deliberately throws away the constant factor. Once you've picked the right curve, that discarded constant is the entire remaining game. Two algorithms at the same O(n) can differ by 5× or 50×, depending on how kindly they treat the machine from Volume 1. Is the data they walk already in the CPU's fast cache? Are the branches they take predictable? Can the work spread across cores? Here are two pairs, each with identical Big-O, timed on this machine:

metal.pypython
data = list(range(5_000_000))

# both O(n): sum by hand vs. the built-in
def manual():
    t = 0
    for x in data: t += x
    return t

def sum_data(): return sum(data)
from timeit import Timer
def best(f): return min(Timer(f).repeat(repeat=5, number=1)) * 1e3   # ms, best of 5

print("manual loop:", round(best(manual), 1), "ms")   # ~113 ms (same Big-O)
print("builtin sum:", round(best(sum_data), 1), "ms")  # ~16 ms  (~7x faster)

Both loops touch all five million items exactly once — flatly, provably O(n), and the doubling probe gives ratio 2 for each. Yet on this run the hand-written loop took about 113 ms and the built-in sum() about 16 ms. That's roughly 7× apart at the same complexity (wall-clock on one machine; your numbers will differ, but the ordering won't). The gap is pure constant. The interpreter re-dispatches Python bytecode on every pass of the hand loop, while sum() runs the same additions in a tight C loop with far less overhead per element. Same curve, very different constant. The second pair shows the memory hierarchy directly:

metal.pypython
grid = [[1]*2000 for _ in range(2000)]   # 4,000,000 cells

def row_major():           # walk each row fully before the next
    s = 0
    for r in range(2000):
        row = grid[r]
        for c in range(2000): s += row[c]
    return s
# row-major: 131 ms   |   col-major (swap the loops): 200 ms  -> ~1.5x, same O(R·C)

Both nested loops visit all four million cells — identical O(R·C). But row_major walks memory the way it's laid out. It grabs one row (a contiguous run of references) and marches straight along it, so the values it needs next are already sitting in cache. Swap the loops to go column-first, and every single step jumps to a different row-list. That defeats the cache and costs about 1.5× more here (131 ms vs 200 ms) for the very same additions. In pure Python the interpreter dilutes the effect; in the C or Rust underneath your libraries the same swap can cost 5–10×. The Big-O saw none of this — the machine saw all of it.

input size n → c·n (big constant) c′·n (cache-friendly) both are O(n) — same shape, the constant sets the height.
Fig — Big-O picks the shape; the constant picks the height. Once your curve is right, machine sympathy is where the last order of magnitude hides.

Myth

"Same Big-O means the same speed — the constant doesn't matter."

Reality

The constant is exactly what separates two O(n) programs that finish 7× apart. Big-O tells you which one wins eventually, as n → ∞. At the n you actually have, a cache-friendly, branch-predictable, vectorizable constant is often the whole ballgame. Get the curve right first — then fight for the constant.

That's the engineering. But the deepest payoff of this whole volume is that the reflexes work off the computer entirely — on your errands, your bugs, your decisions →

05Run the loop on your life

The reason these ideas feel powerful is that they were never really about code. They're about structure, and structure is everywhere. Once the reflexes are in, you start applying them without noticing. That's the real "you use this every day" moment.

everyday.pypython
import math
# BINARY-SEARCH your debugging: a bug appeared somewhere in your commit history.
# "good before, broken after" is monotonic -> bisect it, don't read every commit.
for commits in (100, 1000, 100_000, 1_000_000):
    print(commits, "commits ->", math.ceil(math.log2(commits)), "checks")
# 100 -> 7   |   1000 -> 10   |   100000 -> 17   |   1000000 -> 20

That's git bisect, and it's the binary-search chapter wearing overalls. A million commits hide a bug. Reading them one by one is O(n) — a million checkouts. But "works before the bad commit, fails after" is monotonic, and monotonic is a trigger for binary search: test the middle, throw away half, repeat. We computed it: 20 checks, not a million. That's O(log n) applied to your own workflow, and it's why "bisect it" is the fastest debugging advice anyone will ever give you. The other techniques transfer just as cleanly:

everyday.pypython
# EXPECTED-VALUE your risks (quant thinking for life): train vs. drive.
p_delay, delay = 0.10, 40         # 10% chance the train loses 40 minutes
train = 25 + p_delay * delay      # base 25 min + expected delay
drive = 35                        # flat, "safe"
print(round(train,1), "vs", drive)   # 29.0 vs 35  -> take the "risky" train

The train feels risky because of that 40-minute tail. But expected value — probability × cost — puts the delay at just 0.10 × 40 = 4 minutes. So the train averages 29 minutes against the drive's 35. The "safe" choice was slower. That's the quant's core move (the randomization chapter): don't react to the scary outcome, weight it by its probability. The rest fold in the same way — greedy your errands (visit the nearest stop next, ch39), hash your memory (write it down so recall is O(1) instead of re-deriving it, ch37), and DP your decisions (never re-solve a problem you've already solved — cache the answer, ch40).

1,000,000 commits — "good ✓ … somewhere it breaks ✗" test middle → keep this half the bad commit each test halves what's left → ~20 tests total, not 1,000,000
Fig — git bisect is binary search on your own history. The same halving that finds a name in a phone book finds the commit that broke the build.
The transfer is the point
You already binary-search a phone book and expected-value a bet without naming them. Naming them is the upgrade: once "monotonic → halve it" and "scary tail → weight by probability" are words in your head, you reach for them deliberately, on problems where the structure was hidden. That's what it means to think algorithmically about things that aren't algorithms.

So how do you drill these until they're automatic — until you see in growth curves and triggers without trying? →

06The gap is a loop, run relentlessly

Here is the honest, slightly deflating, deeply encouraging truth this volume has been building toward: the distance between the 99% and the 1% is not talent. It is not a gift you were or weren't born with. It is this handful of reflexes, practiced until they became how you see — and reflexes are trainable. Five drills turn the ideas in this book into instinct:

what's n? where's the waste? ask them once → a chore ask them 10,000 times → instinct
Fig — Two questions, asked until they disappear into reflex. The 1% aren't asking harder questions — they've asked these so often they no longer notice they're asking.
Why this compounds
Every problem you meet is training data for the tree. Solve one with hashing and the next "have I seen this?" jumps out faster. The reflexes reinforce each other: better complexity-reading makes "where's the waste?" sharper, which makes the right trigger fire sooner, which teaches you the technique deeper. This is a positive feedback loop pointed at your own judgment. Run it for a year and you won't recognize how you used to think.

That is the whole message of Volume 3, and it's smaller than it looked from the outside. Count the work. Watch the growth. Match the signature to the tool. Brute-force, find the waste, delete it, re-check. Respect the constant. Then run that loop on everything you touch — code and otherwise — until it stops being something you do and becomes something you are. That's the 1%. Not a different kind of mind. The same mind, running a better loop, relentlessly.

Where the series goes next. You now think about cost like an engineer. The next volume gives you the vocabulary of structure to act on it: the 13 must-know data structures — dynamic arrays, linked lists, stacks and queues, hash maps, binary search trees, heaps, tries, graphs, and the rest — each one a deliberate trade of space for time, and each the reason some operation in this volume was O(1) instead of O(n). You've learned to read the price. Next, you learn to build the machines that set it. →

PROGRAMS run them line by line — the same ideas, written the many ways Python offers
Chapter 47, in working code

Twelve tiny programs that never once touch a stopwatch. They count the work in plain integers, so you can watch the doubling ratio hand you the exponent, a quadratic collapse into a line, and a git bisect crack a million commits in twenty checks — the whole volume folding into one way of seeing.

Derive it — never memorize it
The 1% never recite a table. They double the input, watch how much the work multiplies, and read the exponent straight off the ratio — 2 means O(n), 4 means O(n²), 8 means O(n³).
The loop — brute-force, find the waste, kill it
Solve it the dumbest way that works, count the baseline, then ask the one question: where is the wasted work? Two-sum re-scans for a partner over and over — 'have I seen this value?' is a hashing trigger, and the quadratic collapses into a line.
Respect the constant — machine sympathy
Big-O is necessary, not sufficient: it throws away the constant on purpose, and once the curve is right that discarded constant is the whole remaining game. Two programs at the same O can finish worlds apart — here, counted, not timed.
Run the loop on your life
The reflexes were never really about code. Binary-search your debugging, expected-value your risks, and remember answers instead of re-deriving them — the whole toolkit, transferred, and every count reproducible on any machine.
end of chapter 47 · six sections
iolinked.com
Written by Ajai Raj