# Measure first, then make it fast

Express course on performance. 20 concepts — Profiling, Latency, Throughput, Batching, Bottleneck, Amdahl's Law, Big-O, N+1 Query, Caching, Percentiles, Tail Latency, Premature Optimization and more — each with its English name, a diagram, and a card: what it is → how it works → example → catch. A cheat sheet at the end. The core idea: intuition about slowness is usually wrong, so profile, fix the one thing that dominates, and measure again.

- **Author:** Fedor Chishchin
- **Level:** intermediate
- **Reading time:** ~35 minute read
- **Published:** 2026-06-13
- **Source:** https://fedorthinks.com/learn/performance-express

---

## 1. Measure, don't guess

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.

_Diagram: 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.**

_Diagram: A profile shows each function's share of the time. You suspected rendering (8%); one database query eats 80%._

#### Profiling (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.

_Diagram: Measure → change one thing → measure with the same test. Faster — keep it; not faster — revert._

#### Measure–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.

> **The framing question:** "Have I actually measured where the time goes — or am I about to optimise something based on a hunch that's probably pointing at the wrong place?"

> 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.

## 2. Latency and Throughput

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.

_Diagram: Latency is the whole path of one request: network, server work, network back, rendering._

#### Latency (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.

_Diagram: Throughput is the total flow through the system: how many requests per second it gets through._

#### Throughput (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.

_Diagram: 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._

#### Batching (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**.

> **The goal question:** "Am I trying to make each operation feel faster (latency) or handle more total volume (throughput)? They're different, and the fix for one may not help the other."

> 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.

## 3. Find the bottleneck

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.

_Diagram: The request passes through five steps, but 400 ms of the 500 is one database query. That's the bottleneck._

#### Bottleneck (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_.

_Diagram: Fixed the query: 500 → 140 ms. Now rendering is the slowest step, and the next round targets it._

#### Limiting 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.

_Diagram: Make rendering 2× faster — minus 5%. Make the query 2× faster — minus 40%. The gain is capped by the part's share._

#### Amdahl'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**.

> **The bottleneck question:** "What single part dominates the time here — and am I fixing that, or polishing a part that's already fast enough to barely matter?"

> 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.

## 4. The biggest wins

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.

_Diagram: O(n²) shoots up as the input grows; O(n) grows steadily. On big data it's the difference between minutes and milliseconds._

#### Better 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.

_Diagram: Left: 1 query for the list + N queries one by one. Right: a single query for everything._

#### N+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.

_Diagram: A hit is answered from memory. A miss takes the slow path and remembers the result for next time._

#### Caching (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.

_Diagram: Only part of all the work is really needed. The rest is unused data, work done too early, and results thrown away._

#### Doing 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**.

> **The wins question:** "Is the bottleneck a bad algorithm, too many round trips, missing caching, or doing work I could skip — the handful of fixes behind most real speedups?"

> 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.

## 5. Averages and the tail

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.

_Diagram: Nineteen fast requests and one that takes 5 seconds — and the average line still looks calm._

#### Average (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.

_Diagram: A distribution of response times: p50 sits in the crowd of fast ones, p99 far out in the slow tail._

#### Percentiles (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.

_Diagram: A page waits for the slowest of its calls. With 100 calls and 1% of them slow, about 63% of pages hit the tail._

#### Tail 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**.

> **The measurement question:** "Am I looking at the average and feeling fine — or at p99, where the users who are actually suffering show up?"

> 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.

## 6. Premature optimisation

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**.

_Diagram: Of twelve modules, one is hot — that's the one to speed up. The rest stay correct and clear._

#### Premature 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.

_Diagram: The order of steps: correct first, then clear, and only then fast — and only where measured._

#### Correct → 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.

_Diagram: Left — decisions made up front: algorithm, data structure, architecture shape. Right — what gets tuned later, by profile._

#### Performance 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**.

> **The restraint question:** "Am I optimising this because measurement showed it's a real bottleneck — or just because I can, trading clarity for a speed nobody needs?"

> 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.

## 7. Using it well

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.

_Diagram: Measure → find the bottleneck → fix that one → measure again. Target met — stop; not yet — another round._

#### Optimization 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.

_Diagram: p99 drops with each round. Once the curve is under the target — stop; past that is only diminishing returns._

#### Performance 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.

_Diagram: 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

- **Profiling** (profiler): Measure where a program actually spends its time, function by function.
- **Measure–Fix–Measure** (measure again): Every fix is a hypothesis that a second measurement confirms.

### Speed goals

- **Latency** (response time): The time of one operation, start to finish — what a user feels.
- **Throughput** (requests per second): How much work the system handles per second.
- **Batching** (grouping work): Group work together: throughput goes up, and so does latency.

### Bottleneck

- **Bottleneck** (the dominant cost): The one part that takes most of the time.
- **Limiting Step** (the next bottleneck): A system is as fast as its slowest step; fix it and the next one appears.
- **Amdahl's Law** (capped by the share): Your gain is capped by the share of time the sped-up part takes.

### The biggest wins

- **Better Algorithm** (Big-O): Change the approach: O(n²) → O(n) beats any hardware.
- **N+1 Query** (extra round trips): A query per list item; cured by fetching in bulk.
- **Caching** (remember results): Remember an expensive result and don't compute it again.
- **Doing Less** (lazy 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.
- **Percentiles** (p50 / p99): p50 is the typical case, p99 roughly the worst a real user hits.
- **Tail Latency** (the slow tail): The slowest requests; people remember them, and with many calls almost everyone hits them.

### Restraint

- **Premature Optimization** (optimising a guess): Speeding things up before measurement shows the need.
- **Correct → Clear → Fast** (make it work, right, fast): Correct first, then clear, fast only in the hot spots.
- **Performance by Design** (the big shape): Get the big shape (algorithm, structure, architecture) right up front.

### Practice

- **Optimization Loop** (the loop): Measure → fix one bottleneck → measure again → repeat.
- **Performance Target** (fast 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.
