fedorthinks

Express course · No. 24

Performance work goes wrong the same way every time: someone guesses what's slow, optimises the wrong thing, and adds complexity for nothing. The discipline is the opposite — measure to find the real bottleneck, fix that one thing, then measure again. Learn what actually makes software slow, and the words for it, and 'make it faster' becomes a method instead of a guess.

20 concepts · a diagram for each · cheat sheet at the end

§ 01

The first rule of performance is the one everyone breaks: don't trust your hunch about what's slow. Almost all wasted optimisation comes from skipping this single step.

Your intuition about slowness is usually wrong

A doctor who prescribes surgery based on a guess, without ever running a test — confident, fast, and far too often operating on the wrong thing entirely.

Developers are famously bad at guessing where time goes. The part you're sure is slow is often fine; the real culprit is somewhere you never suspected — a tiny function called a million times, a hidden database call.

Acting on a hunch means you optimise the wrong thing, add complexity, and the program stays slow. The first move is never "fix it" — it's find out where the time actually goes.

The map: 20 concepts in seven groups

A metro map: first you see the whole lines, then you ride station by station.

The course's concepts fall into seven groups — one per chapter. First, how to measure. Then which kind of "fast" you need. Then the bottleneck and the classic fixes for it. Then how not to be fooled by the average, when not to optimise, and how to run the whole thing as a loop.

MeasureProfilingMeasure–fix–measureSpeed goalsLatencyThroughputBatchingBottleneckBottleneckLimiting stepAmdahl's lawBiggest winsBetter algorithmN+1 / round tripsCachingDoing lessThe tailAverage trapPercentilesTail latencyRestraintPremature optimizationCorrect → clear → fastPerf by designPracticeOptimization loopPerformance target
Seven groups of concepts — the chapters of the course follow them.

How to read this course

A mechanic doesn't swap parts at random: instruments first, then one fix, then a re-test.

Every concept gets the same treatment: its English name (remember it — it's the term in docs, profilers, and dashboards), a picture from everyday life, a diagram, and a card with what it is, how it works, example, catch, and related.

The big idea: performance isn't a bag of tricks, it's a method. Measure → find the bottleneck → fix that one thing → measure again → stop at a target. Everything else in the course is detail inside that loop.

Profiling — find the real cost

An itemised bill that shows exactly where the money went, line by line — so you stop guessing and see that one charge, not the ones you assumed, ate the budget.

A profiler is a tool that measures where your program actually spends its time, function by function. It turns "I think this is slow" into "this one function is 80% of the runtime." With that, you fix what matters instead of what you imagined.

Whether it's a profiler, timing logs, or your observability metrics, the principle holds: let measurement, not intuition, point you at the problem.

share of runtime, function by functiongetOrders()80%renderPage()8%parseJSON()5%formatDate()4%auth()3%← real cost← where you guessedmeasurement, not intuition, points at the problem
A profile shows each function's share of the time. You suspected rendering (8%); one database query eats 80%.
ToolProfiling· profiler
What it is

Measuring where a program actually spends its time, function by function.

How it works

A profiler (or timing logs, or metrics) records the time of each part and shows the shares.

Example

"getOrders() is 80% of the request" instead of "rendering must be slow."

Catch

A profile on toy data can point the wrong way — measure under a realistic load.

Related

The first step of Measure–Fix–Measure; it's what finds the Bottleneck.

Measure–Fix–Measure — measure, fix, measure again

A scientist changes one variable, re-runs the experiment, and checks the result — never assuming a change helped, always confirming it did.

Performance is a loop: measure to find the slow part, change it, then measure again to confirm the change actually helped — and didn't just move the problem or make it worse.

Without the second measurement you're guessing whether you improved anything, which is how "optimisations" quietly make things slower. Treat every fix as a hypothesis you verify — the same discipline as evals for AI or tests for code.

Measurefind the slow partChangeone thingMeasure againsame testKeep itfaster ✓Revertsame or slowerevery fix is a hypothesis — confirm it with a second measurement
Measure → change one thing → measure with the same test. Faster — keep it; not faster — revert.
PracticeMeasure–Fix–Measure· measure, fix, measure again
What it is
Every fix is checked by a second measurement, before and after.
How it works

The same test before and after; change one thing at a time so you know what worked.

Example

Added an index: the query went from 400 ms to 40 ms — keep it. No change — revert.

Catch

Without the second measurement, an "optimisation" can quietly slow the system and nobody notices.

Related
The core of the Optimization Loop; a cousin of evals and tests.

Intuition about slowness is usually wrong. Profile to find where time really goes, fix that, and measure again to confirm — never optimise on a guess.

§ 02

Before optimising, know which kind of "fast" you actually want. Two different goals get confused constantly, and improving one can do nothing for the other.

Latency — the wait for one thing

How long a single coffee takes from order to cup — one customer's experience, measured start to finish.

Latency is how long one operation takes from start to finish — one request, one page load, one query. It's what an individual user feels: the wait.

When someone says "the site is slow," they almost always mean latency — the delay between doing something and getting a response. Lowering latency is about making each single thing happen faster.

latency = one operation, start to finishnetworkserver worknetworkrenderclickpage readywhat one user feels: the wait
Latency is the whole path of one request: network, server work, network back, rendering.
MetricLatency· response time
What it is

The time of one operation from start to finish, usually in milliseconds.

How it works

It adds up every step on the path: network, server work, database, rendering.

Example
From click to ready page — 800 ms.
Catch

A single latency number is usually an average, and the average hides the slow cases (chapter 5).

Related
A separate goal from Throughput; measured with Percentiles.

Throughput — how much you handle per second

How many coffees the whole café serves an hour — not how long one takes, but the total flow through the shop.

Throughput is how much work the system handles per unit of time — requests per second, jobs per minute. It's about total capacity, not individual wait.

A system can have great throughput (serving thousands at once) while each user still waits a while (high latency), or low latency but limited capacity. They're different goals, and which one you're chasing changes what you fix.

throughput = how much work per secondSystem1,000 req / srequests inrequests donenot how long one takes — the total flow through the system
Throughput is the total flow through the system: how many requests per second it gets through.
MetricThroughput· requests per second
What it is
Work done per unit of time: requests per second, jobs per minute.
How it works

It rises when the system handles more in parallel or spends less on each unit.

Example

An API handles 1,000 requests a second; a nightly pipeline, a million rows an hour.

Catch

High throughput doesn't mean a fast experience: each user can still wait long.

Related
Often raised at the expense of Latency via Batching.

Batching — throughput at the cost of latency

A motorbike gets one person there fastest (low latency); a bus moves the most people per trip (high throughput). The best vehicle depends on which you need.

Latency and throughput are independent, and optimising one can hurt the other. Batching — grouping work together — often raises throughput but adds latency, because each item waits for the batch.

So you have to decide which matters here: a user-facing page lives or dies on latency; a background data pipeline cares about throughput. Naming your real goal keeps you from optimising the number that doesn't matter to your users.

one at a time4 separate trips · each item leaves at onceServerbatched1 trip for all 4 · more per secondServeritems wait for the batchbatching: throughput ↑ · latency ↑
One at a time — each item leaves at once, but there are many trips. Batched — one trip, more per second, but every item waits for the batch.
TechniqueBatching· grouping work
What it is
Collecting many small operations into one big one.
How it works

Overhead (a network trip, starting a job) is paid once per batch, not once per item.

Example
Insert 1,000 rows in one query instead of 1,000 separate ones.
Catch
Each item waits for the batch to fill — latency goes up.
Related

Trades Latency for Throughput; the same idea that cures the N+1 Query.

Latency is the wait for one operation; throughput is how much you handle per second. They're independent goals — and batching often trades latency for throughput.

§ 03

Performance isn't spread evenly. Almost always, one part dominates the time — and optimising anything else is wasted effort until you fix that one.

Bottleneck — one slow part dominates

A chain is exactly as strong as its weakest link — reinforcing the strong links does nothing; only the weak one decides whether it holds.

In most slow systems, a single bottleneck accounts for the bulk of the time — one query, one slow service, one bad loop. Everything else is already fast enough.

This is why guessing is so wasteful: the whole skill is locating the dominant cost, not improving the fast parts.

Auth10 msLoad user15 msQuery orders400 msLogic25 msRender50 ms80% of the timetotal: 500 msone step dominates — that is the bottleneckthe other four together are 100 ms
The request passes through five steps, but 400 ms of the 500 is one database query. That's the bottleneck.
ConceptBottleneck· the dominant cost
What it is
The one part that takes most of the total time.
How it works

A profile shows the shares; usually one part is far bigger than the rest.

Example

A page takes 500 ms, and 400 of it is one query on an unindexed column.

Catch
The bottleneck is rarely where you expect it — hence profile first.
Related

Found with Profiling; Amdahl's Law explains why it's the thing to fix.

Limiting Step — the system is only as fast as its slowest step

A highway that's wide open until one lane-closure where every car crawls — the whole journey's speed is set by that one chokepoint, not the clear stretches.

A request that flows through ten steps is limited by the slowest one — the limiting step. Speeding up the nine fast steps barely moves the total; the slow step sets the pace. So you target the chokepoint specifically.

And note that fixing one bottleneck often reveals the next: remove the slowest step and a new slowest step emerges. Performance work is iteratively finding and clearing whatever is limiting the system right now.

beforeQuery 400 msRenderfix the query (400 → 40 ms)afterQueryRender500 → 140 msRender is now the slowest stepfix one bottleneck → the next one shows up
Fixed the query: 500 → 140 ms. Now rendering is the slowest step, and the next round targets it.
ConceptLimiting Step· the next bottleneck
What it is
The step that currently sets the speed of the whole path.
How it works

Remove one limiting step and the next-slowest becomes the limiting one.

Example

The query got 10× faster — now 50 ms of rendering is the slowest step.

Catch

The bottleneck never disappears for good, it moves — which is why you need a target to stop at.

Related

Why the Optimization Loop is a loop, and why Performance Target is its exit.

Amdahl's Law — optimising elsewhere is wasted effort

Polishing the trophy while the car has a flat tyre — effort spent where it changes nothing, while the thing that actually stops you sits ignored.

Optimise a part that's only 2% of the runtime and the best you can win is 2%. Find the part that's 80%, and fixing it transforms everything. This arithmetic is called Amdahl's law: your gain is capped by the share of time the part you speed up actually takes.

Time spent optimising anything but the bottleneck is, by definition, time that can't meaningfully help. It often makes things worse, adding complexity and bugs for an invisible gain. Measurement stops you from lovingly optimising the part you understand and forces you onto the part that actually costs. Fix the bottleneck, ignore the rest — until the rest becomes the bottleneck.

speed up Render 2× (10% of the time)Query475 ms · −5%speed up Query 2× (80% of the time)QueryRender300 ms · −40%the win is capped by the share of time that part takes
Make rendering 2× faster — minus 5%. Make the query 2× faster — minus 40%. The gain is capped by the part's share.
LawAmdahl's Law· the gain is capped by the share
What it is

Speeding up part of a system is limited by the share of time that part takes.

How it works

A part that is 10% of the time can save at most 10%, even if made infinitely fast.

Example

Rendering (10%) 2× faster: 500 → 475 ms. The query (80%) 2× faster: 500 → 300 ms.

Catch

It feels good to optimise what you understand — but if it isn't the bottleneck, there's no win.

Related

The maths behind Bottleneck; the case against Premature Optimization.

One bottleneck usually dominates the time, and the system is only as fast as its slowest step. Fix that one thing; optimising anything else is wasted until it becomes the bottleneck.

§ 04

When you do find the bottleneck, the fix is usually one of a small number of classics. These few account for the overwhelming majority of real-world speedups.

Better Algorithm (Big-O) — a better algorithm beats a faster machine

Two routes to the same city: a faster car on the long winding road still loses to a slower car on the straight shortcut. The route matters more than the engine.

The biggest wins often come from a better algorithm or data structure, not faster hardware or micro-tweaks. Big-O describes how the time grows as the input grows. An O(n²) loop replaced with an O(n) hash-map lookup can turn minutes into milliseconds — a change no amount of faster machine could match.

Before optimising the small stuff, ask whether the approach is wrong. A better shape, as the data-structures course shows, is the cheapest and largest speedup there is.

input size n →timeO(n²) — nested loopO(n) — hash-map lookupminutes → milliseconds
O(n²) shoots up as the input grows; O(n) grows steadily. On big data it's the difference between minutes and milliseconds.
TechniqueBetter Algorithm· Big-O, algorithmic complexity
What it is
Changing the approach so the work grows more slowly with the data.
How it works

Big-O shows the growth: O(n²) is a nested "everyone with everyone" loop, O(n) is one pass.

Example

Finding the common items of two lists: nested loop → a hash set and a single pass.

Catch

On small data there's no visible difference — it explodes as the data grows.

Related

The main choice in Performance by Design; see the data-structures course.

N+1 Query — too many round trips

Making twenty separate trips to the shop for one item each, instead of one trip with a list — the travel, not the shopping, is eating your afternoon.

A huge amount of slowness is too many round trips — repeated calls to a database or service, each cheap alone but devastating in bulk.

The classic is the N+1 query: fetching a list, then making one more query per item, turning one trip into hundreds. The fix is to fetch in bulk — ask for everything at once. Reducing the number of trips usually beats speeding up each one, because the round trip itself is the cost.

N+1 queriesAppDBlistorder 1order 2order 3…order N1 + N round tripsBulk fetchAppDBall at onceall rows1 round trip
Left: 1 query for the list + N queries one by one. Right: a single query for everything.
Anti-patternN+1 Query· extra round trips
What it is
One query for a list, then one query per item.
How it works

Every trip to the database costs network and overhead; N trips multiply that cost by N.

Example

100 orders → 1 query for the list + 100 queries for the customers. Fix: one query with IN (…) or a JOIN.

Catch

ORMs hide it: the code looks like a simple loop, and underneath are hundreds of queries.

Related
Cured by the same idea as Batching; a frequent Bottleneck.

Caching — don't compute the same thing twice

Re-cooking a meal from scratch every time someone asks for it, instead of keeping a pot warm — the fastest work is the work you don't repeat.

Caching — remembering an expensive result so you don't recompute it — is one of the biggest levers there is (it has its own course).

The first request takes the slow path and saves the result; the next ones get it from memory. The price is memory and the risk of serving stale data.

RequestCacheseen it?Returnfrom memory · fastCompute / DBslow, expensivehitmissremember resultthe second ask is answered without redoing the work
A hit is answered from memory. A miss takes the slow path and remembers the result for next time.
TechniqueCaching· remember expensive results
What it is
Storing the result of expensive work and serving it again.
How it works

Check the cache: there — return it at once (hit); not there — compute, store, return (miss).

Example

A catalogue page takes 300 ms to build; from the cache, a couple of milliseconds.

Catch

A cache can serve stale data; when to clear it is a hard problem of its own.

Related

A special case of Doing Less; covered in depth in the caching course.

Doing Less — the fastest work is the work you skip

Not cooking what nobody ordered, and not laying the table for twenty when three turned up.

One more classic — doing less. Compute things lazily, only when actually needed; avoid loading data you won't use; skip work whose result you'll throw away.

The fastest operation is the one you never run. Often the best optimisation isn't doing the work faster — it's not doing it at all.

before: all the workneeded nowunused data ✗not needed yetthrown away ✗after: do lessneeded nowthe fastest work is the work you skipcompute lazily · don't load what you won't use · don't make what you'll discard
Only part of all the work is really needed. The rest is unused data, work done too early, and results thrown away.
TechniqueDoing Less· lazy evaluation
What it is
Removing work instead of speeding it up.
How it works

Lazy computation, loading only the fields you need, dropping results nobody uses.

Example

Pull the 3 fields you need from the database, not all 50; build a report only when it's opened.

Catch
Lazy work can bite later, at an awkward moment — measure.
Related

Includes Caching (don't repeat) and cures part of the N+1 Query.

Most speedups are a few classics: a better algorithm, fewer round trips (kill the N+1), caching expensive results, and simply doing less work. The fastest work is the work you skip.

§ 05

How you measure performance can lie to you. The average is the most comforting and most misleading number, and learning to look past it is what separates real performance work from wishful thinking.

Average — the average hides the slow cases

A room with nine comfortable people and one on fire has a pleasant average temperature — the mean erases the case that actually matters.

Reporting performance as an average (the mean) is dangerously soothing: it blends the fast majority with the slow few into one happy number. But users don't experience the average — each user experiences their request, and the slow ones feel every millisecond.

An average response time of 200 ms can hide that one user in twenty waits five seconds. The mean tells you the system is fine while a real slice of your users suffer.

average — looks fine1 in 20 waits 5 s20 requests
Nineteen fast requests and one that takes 5 seconds — and the average line still looks calm.
MetricAverage (Mean)· the average trap
What it is
The sum of the times divided by the number of requests.
How it works
It blends fast and slow requests into a single number.
Example

"The average is 200 ms" — while one request in twenty waits 5 seconds.

Catch
Looks respectable while some users suffer. Never set targets on it.
Related
Replaced by Percentiles; hides Tail Latency.

Percentiles — p50 and p99 show the real experience

A report that says not just the typical wait but "the slowest 1% of people waited this long" — naming the bad experiences instead of averaging them away.

Percentiles capture what the average hides. p50 (the median) is the typical case — half are faster. p99 is the slow tail — 99% are faster, so it's roughly the worst experience a real user hits.

Watching p99, not just the average, is how you see the users who are actually suffering. The tail is where the pain lives, and percentiles are how you make it visible.

p50 — half are fasterp99 — 99% are fasterthe slow taillatency →many requests are fast
A distribution of response times: p50 sits in the crowd of fast ones, p99 far out in the slow tail.
MetricPercentiles· p50 / p95 / p99
What it is

pN is the time that N% of requests beat. p50 is the median, p99 the tail.

How it works

Sort the response times and take the value at the right position.

Example

p50 = 120 ms (typical), p99 = 2 s (one request in a hundred waits two seconds).

Catch

You can't average percentiles across servers — compute them over all requests at once.

Related
Make Tail Latency visible; Performance Target is set on them.

Tail Latency — what users remember

One terrible meal at a restaurant outweighs a dozen fine ones in someone's memory — the worst experiences, not the average, shape how people judge you.

The slow requests — the tail latency — disproportionately shape how users perceive your product, because a bad experience sticks.

And at scale, the tail hits more people than it seems: if every page makes many calls, the chance that at least one is slow grows fast, so almost every user eventually feels the tail. This is why serious teams set targets on p99, not averages: taming the worst cases is what makes a product feel reliably fast.

Pageone user viewPage donewaits for the slowestcall 1 · 30 mscall 2 · 25 mscall 3 · 40 mscall 4 · 2 scall 5 · 35 ms100 calls × 1% slow each → ~63% of pages hit the tail
A page waits for the slowest of its calls. With 100 calls and 1% of them slow, about 63% of pages hit the tail.
ConceptTail Latency· the slow tail
What it is
The time of the slowest requests — p99 and beyond.
How it works

When one response is assembled from many calls, it waits for the slowest of them.

Example

Each call is slow only 1% of the time, yet a page of 100 calls hits the tail about 63% of the time.

Catch
The more calls per response, the more a "rare" tail becomes the norm.
Related
Seen through Percentiles; hidden by the Average.

Averages hide the slow cases that users actually feel. Watch percentiles — p50 for typical, p99 for the painful tail — because the worst experiences are what people remember.

§ 06

There's a failure mode opposite to ignoring performance: chasing it too early, everywhere, before you know it matters. The famous warning about it is famous for a reason.

Premature Optimization — the root of all evil

Reinforcing every wall of a house against an earthquake that may never come, before anyone has even moved in — enormous effort spent on a problem you don't yet have.

Donald Knuth's famous line — "premature optimization is the root of all evil" — warns against optimising before you know what, or whether, anything needs it.

Optimising code that isn't slow, or isn't even run much, buys nothing and costs plenty: complexity, bugs, and time stolen from things that matter. Most code doesn't need to be fast; it needs to be correct and clear. Speed work is for the parts measurement proves are hot.

settingsadminexportsignupreportsemailscheckouthot · measured 70%searchbillingprofilelogsimportspeed work only where measurement shows heateverything else: keep it correct and clear
Of twelve modules, one is hot — that's the one to speed up. The rest stay correct and clear.
Anti-patternPremature Optimization· optimising a guess
What it is
Optimising before measurement has shown it's needed.
How it works

You pay with clarity and time for speed in a place where nobody needs it.

Example

A week on a clever cache for a settings page that's opened once a month.

Catch

Tangled "fast" code gets in the way when the real bottleneck turns up somewhere else.

Related

Cured by Profiling; explained by Amdahl's Law; not to be confused with Performance by Design.

Correct → Clear → Fast — clear and correct first, fast where it counts

You write the recipe so anyone can follow it, and only streamline the one step you make a hundred times a day — not every step, just the hot one.

The order is: make it correct, make it clear, and make it fast only where measurement shows it matters.

Clear code is easier to optimise later precisely because you can understand it; tangled "fast" code is hard to fix when the real bottleneck appears elsewhere. Optimising for speed almost always costs readability, so you spend that cost only where it buys a real, measured win — and keep the rest simple.

make it work → make it right → make it fast1. Correctdoes the right thing2. Cleareasy to read and change3. Fastonly where measured hotclear code is easier to speed up later — tangled "fast" code is not
The order of steps: correct first, then clear, and only then fast — and only where measured.
PrincipleCorrect → Clear → Fast· make it work, make it right, make it fast
What it is
An order of priorities: correctness, clarity, and only then speed.
How it works

Speed is added surgically in hot spots, once clear code exists and has been measured.

Example

Write a plain, readable handler; the profile shows one hot loop — optimise only that.

Catch

Optimisation almost always costs readability — spend it only on a real win.

Related
The antidote to Premature Optimization.

Performance by Design — but don't ignore performance in the design

You don't earthquake-proof a shed, but you also don't build a skyscraper on sand — some choices are cheap to get right early and ruinous to fix late.

The warning isn't "never think about performance." Choosing an O(n²) algorithm where an O(n) one was just as easy, or a structure that won't scale, is a design mistake you'll pay for dearly later.

The balance: don't micro-optimise early, but don't make architectural choices that are slow by design and expensive to undo. Get the big shape right cheaply up front; optimise the details only when measurement demands it.

Decide earlyTune later, if measuredO(n) vs O(n²) algorithmdata structure that scalesarchitecture shapemicro-tweakstuning a hot functionclever speed trickscheap now, ruinous to change lateronly when a profile asks for it
Left — decisions made up front: algorithm, data structure, architecture shape. Right — what gets tuned later, by profile.
PrinciplePerformance by Design· the big shape, up front
What it is

Making the big decisions about a system's shape with speed in mind from the start.

How it works

Algorithm, data structure, and architecture are chosen early; micro-tuning comes later, by measurement.

Example

Choosing an indexed lookup over scanning the whole table from day one — that isn't premature.

Catch

It's easy to hide ordinary premature optimisation of details behind this principle.

Related

The boundary of Premature Optimization; its main tool is Better Algorithm.

Premature optimisation costs clarity for speed nobody needs. Make it correct and clear first, fast only where measurement proves it matters — but don't choose a slow-by-design shape you'll regret.

§ 07

Performance done well is a calm, repeatable method, not a heroic scramble. The whole practice fits in a short loop you run only when there's a real reason to.

Optimization Loop — measure, fix the bottleneck, repeat

A mechanic diagnoses with instruments, fixes the one failing part, then re-tests — never replacing random parts hoping the noise goes away.

The method is simple and disciplined: measure to find the bottleneck, fix that one thing, measure again to confirm and to reveal the next bottleneck, and stop when it's fast enough.

Each pass targets the current dominant cost and ignores everything else. This loop keeps performance work honest and efficient — you always know you're working on the thing that actually matters, and you always know whether you helped.

MeasureFind bottleneckFix that oneMeasure againStop ✓not yettarget met
Measure → find the bottleneck → fix that one → measure again. Target met — stop; not yet — another round.
PracticeOptimization Loop· the performance loop
What it is
A repeating cycle: measure, find, fix one thing, check.
How it works

Each round is one Measure–Fix–Measure against the current Limiting Step.

Example

Round 1: an index for the query. Round 2: rendering is now the bottleneck — cache the template. Target met — stop.

Catch
Without a target, the loop never ends.
Related

Ties Profiling, Bottleneck, and Performance Target together.

Performance Target — set a target, then stop

You insulate the house until it's warm enough, then stop — you don't keep adding insulation forever past the point anyone can feel.

Performance has no natural end — you can always make something marginally faster. So set a performance target ("pages under 300 ms at p99") and stop when you hit it.

Without a goal, optimisation becomes an endless time-sink with diminishing returns, and complexity creeps in for gains nobody notices. Knowing what "fast enough" means — for your users, your use case — is what lets you do exactly enough performance work and then go build something else.

p99 latencyiterations →target: p99 under 300 msstop here ✓diminishing returns
p99 drops with each round. Once the curve is under the target — stop; past that is only diminishing returns.
PracticePerformance Target· performance budget
What it is
A concrete "fast enough" number, tied to a percentile.
How it works

The optimisation loop runs while the metric is above the target and stops once it's below.

Example
"Home page p99 under 300 ms."
Catch
A target on the average will fool you — set it on p99.
Related
The exit of the Optimization Loop; measured with Percentiles.

How it all fits together

The whole route on one map: where you start, which stations you pass, and where you get off.

The concepts form one picture. Profiling finds the bottleneck; the goal — latency or throughput — tells you what to fix. The bottleneck is usually cured by one of four classics: a better algorithm, fewer round trips, a cache, or doing less. Then you measure again, by percentiles, against a target, and stop. And premature optimisation is what happens when you skip the measuring.

ProfilingBottleneckLatency / ThroughputfindsgoalBetter algorithmN+1 / round tripsCachingDoing lessmeasure againPercentiles / p99Perf targetOptimization loopset onstopsPremature optimization ✗skip measuring → optimise a guess, add complexity
Profile → bottleneck → one of the classic fixes → measure again → a p99 target stops the loop. At the bottom — what happens without measurement.
Before you optimise
  • Have I measured where the time actually goes, or am I guessing?
  • Latency or throughput — which kind of fast do I actually need?
  • What's the bottleneck — the one part that dominates the time?
  • Is it a classic — bad algorithm, too many round trips, missing cache, needless work?
  • Am I watching p99, not just the average that hides the slow tail?
  • Is there a target I'm optimising toward, so I know when to stop?
Signs you optimise well
  • You measure before touching anything, and measure again after.
  • You fix the bottleneck and ignore the parts that are already fast enough.
  • Your wins come from algorithms, fewer round trips, and caching — not micro-tweaks.
  • You judge speed by p99, not a flattering average.
  • You optimise toward a target and stop, keeping the rest clear and correct.

Cheat sheet: all 20 concepts

Measure

Profilingprofiler

Measure where a program actually spends its time, function by function.

Measure–Fix–Measuremeasure again

Every fix is a hypothesis that a second measurement confirms.

Speed goals

Latencyresponse time

The time of one operation, start to finish — what a user feels.

Throughputrequests per second

How much work the system handles per second.

Batchinggrouping work

Group work together: throughput goes up, and so does latency.

Bottleneck

Bottleneckthe dominant cost

The one part that takes most of the time.

Limiting Stepthe next bottleneck

A system is as fast as its slowest step; fix it and the next one appears.

Amdahl's Lawcapped by the share

Your gain is capped by the share of time the sped-up part takes.

The biggest wins

Better AlgorithmBig-O

Change the approach: O(n²) → O(n) beats any hardware.

N+1 Queryextra round trips

A query per list item; cured by fetching in bulk.

Cachingremember results

Remember an expensive result and don't compute it again.

Doing Lesslazy evaluation

Skip needless work: lazily, only the data you need, no thrown-away results.

The tail

Average (Mean)the average trap

One calm number that hides the slow cases.

Percentilesp50 / p99

p50 is the typical case, p99 roughly the worst a real user hits.

Tail Latencythe slow tail

The slowest requests; people remember them, and with many calls almost everyone hits them.

Restraint

Premature Optimizationoptimising a guess

Speeding things up before measurement shows the need.

Correct → Clear → Fastmake it work, right, fast

Correct first, then clear, fast only in the hot spots.

Performance by Designthe big shape

Get the big shape (algorithm, structure, architecture) right up front.

Practice

Optimization Loopthe loop

Measure → fix one bottleneck → measure again → repeat.

Performance Targetfast enough

A "fast enough" number at p99 — where the loop stops.

Performance is a calm loop: measure, fix the one bottleneck, measure again, stop at a target. Reach for it only where it's proven to matter, and keep everything else clear.

End of express course · 7 chapters · 20 concepts

Next comes practice: take something that feels slow, and instead of guessing, actually profile it — find the one function or query that dominates, fix only that, and measure the before and after. Then check its p99, not just the average. The method proves itself the moment a single targeted fix, found by measurement, does more than a week of hunches would have. But hold one idea above the rest: measure first. Almost every performance disaster is someone optimising what they guessed instead of what they measured — and almost every real win is finding the one bottleneck that mattered and fixing exactly that.