Visual walkthrough — Write-allocate vs no-allocate
This is the visual companion to the parent topic. Read it slowly; each step has a picture that carries the argument.
Step 0 — The vocabulary, drawn before it is used
Before we can talk about "write miss" we need three plain pictures. Nothing here is assumed.

Look at the picture: the long grey street is memory, the small yellow shelf is the cache, and the dashed bracket shows one line = 8 neighbouring boxes copied as a unit. The single blue arrow is a write landing on a box that is not on the shelf — that is a write miss, and it is the only situation this whole page is about.
Recall Why do we only care about write
misses? Because a write hit is boring — the box is already on the shelf, you just change it. ::: The interesting decision (fetch the neighborhood, or not?) only appears when the box is absent from the cache.
Step 1 — The fork: what a write miss forces us to decide
WHAT. A write miss just happened. We must now choose one of two behaviours.
WHY. The cache is small. Every byte we put on the shelf pushes some other byte off. So "should this written byte earn a shelf spot?" is a real cost question, not a free choice.
PICTURE.

The blue write hits an empty line. Two chalk arrows leave the fork:
- Left road — write-allocate (also called fetch-on-write): pull the whole 8-box block onto the shelf, then change the one byte.
- Right road — no-allocate (also called write-around): send the byte around the shelf, straight to memory. The shelf stays empty.
Everything below is just counting the consequences of taking each road.
Step 2 — Road A traced: write-allocate, box by box
WHAT. Follow the left road for a single write to address 0x28 (value 0xFF), on a cache with 8-byte lines.
WHY. We must see why fetching the whole block — not just the one byte — is unavoidable.
PICTURE.

Trace the numbered chalk arrows:
- Miss on
0x28— the target line is empty. - Fetch the entire block
0x28–0x2F(all 8 neighbours) up from memory. This is the pink "read" arrow going up. - Write
0xFFinto the byte at0x28on the shelf (blue). - Mark dirty — the dirty bit defined in Step 0 is set, flagging this shelf copy as newer than memory.
Cost so far: exactly 1 memory read (8 bytes). Zero writes to memory yet — the dirty byte waits on the shelf.
Step 3 — Road B traced: no-allocate, box by box
WHAT. Same write to 0x28, now on the right road, paired with write-through.
WHY. To see that the shelf stays empty and the byte skips it entirely.
PICTURE.

- Miss on
0x28. - No-allocate — do not touch the shelf.
- Write-through — the byte flows down the pink arrow straight into memory box
0x28. - The cache line stays empty (invalid).
Cost so far: 1 memory write. No read, no fetch, no shelf space used.
Recall Side-by-side, what did each single write cost?
Write-allocate: 1 read (dragged in 8 neighbours). ::: No-allocate: 1 write (byte only). For one isolated write no-allocate is cheaper — the payoff of allocate only appears when neighbours get reused, which is Step 5.
Step 4 — The forced marriage: why write-back demands write-allocate
WHAT. We now prove a pairing rule, not just state it. Write-back can only sit with write-allocate.
WHY. Write-back means: "I keep the only fresh copy on the shelf and write it to memory later, when the line is evicted." Let us try to combine that with no-allocate and watch it break.
PICTURE.

Follow the contradiction on the board:
- Write-back's promise: don't write to memory now — write later, from the shelf.
- No-allocate's rule: the byte is not on the shelf.
- So "write it later, from the shelf" is impossible — there is no shelf copy to write later. The only escape is to write to memory now.
- But "write to memory now" is write-through behaviour!
Step 5 — The counting that decides everything: four writes to one block
WHAT. Write 0x100, 0x104, 0x108, 0x10C (all inside the same 8-byte line — the line 0x100–0x107... wait, check the arithmetic below), then read 0x100. Count memory trips on each road. This is the payoff calculation.
WHY. One isolated write favoured no-allocate (Step 3). But real programs write neighbours — this is spatial locality. We must see how the tally flips.
PICTURE.

Read the two chalk columns (all four writes now genuinely share the line 0x100–0x107):
| Step | Write-allocate + write-back | No-allocate + write-through |
|---|---|---|
write 0x100 |
miss → 1 read (fetch line) | miss → 1 write |
write 0x102 |
hit (same line, on shelf) | miss → 1 write |
write 0x104 |
hit | miss → 1 write |
write 0x106 |
hit | miss → 1 write |
read 0x100 |
hit | miss → 1 read |
| Total | 1 memory trip | 5 memory trips |
WHY the gap. On the left, the one fetch dragged the whole line 0x100–0x107 onto the shelf, so writes 2–4 and the final read are all free hits because they land in that same line. On the right, each byte is a fresh independent trip because nothing is ever cached. Spatial locality is exactly what write-allocate monetizes.
Step 6 — The third quadrant: write-through + write-allocate
WHAT. We have traced write-allocate+write-back (Step 2/5) and no-allocate+write-through (Step 3). The remaining valid combination is write-through + write-allocate — fetch the line on a miss, but still push every write down to memory immediately.
WHY. Step 4 killed write-back+no-allocate as a contradiction. But write-through+write-allocate is perfectly consistent, and it fills the last box of the design grid, so the reader never meets an un-shown quadrant.
PICTURE.

On this road, the same four-write-then-read sequence behaves like this: the first write misses and fetches the line (1 read), so all later writes are read-hits — but because it is write-through, each write also spills down to memory (4 writes), and the final read is a hit.
| write-through + write-allocate | |
|---|---|
write 0x100 |
miss → 1 read (fetch) + 1 write-through |
write 0x102 |
hit + 1 write-through |
write 0x104 |
hit + 1 write-through |
write 0x106 |
hit + 1 write-through |
read 0x100 |
hit |
| Total | 1 read + 4 writes = 5 trips |
Step 7 — The degenerate case: streaming writes, where the tally flips back
WHAT. Now write a huge array once and never read it — a video frame buffer.
WHY. This is the case that breaks the naive "allocate always wins." When reuse probability is zero, the fetch in Step 2 buys nothing and actively harms.
PICTURE.

for (int i = 0; i < 1920*1080; i++)
framebuffer[i] = pixel_value; // written once, never readOn the write-allocate road, each new line triggers a useless fetch (reading pixels we are about to overwrite entirely), and worse, it evicts genuinely useful data off the shelf — this is cache pollution. The right road (no-allocate) avoids both.
This is exactly why CPUs offer non-temporal / streaming store instructions (e.g. movnti), often draining through write-combining buffers so the many bytes still leave as few wide memory writes.
The one-picture summary

The final board compresses the whole derivation: the fork (Step 1), the two costs of one write (Steps 2–3), the forced marriage with write-back (Step 4), the complete 2×2 grid (Steps 5–6), and the streaming degenerate case (Step 7).
Recall Feynman retelling — say it like a story
Memory is a long street of numbered boxes; the cache is a tiny fast shelf that only ever grabs a whole line of 8 neighbouring boxes at once. When you write to a box that is not on the shelf, you hit a fork.
:::
Road one, write-allocate: haul the whole 8-box line up (one read), change your byte on the shelf, flag it dirty. Costs one read now, but every neighbour in that same line you touch afterwards is a free hit — so writing four bytes inside line 0x100–0x107 plus reading one back is just one trip to memory (when paired with write-back).
:::
Road two, no-allocate: shove the byte straight past the shelf into memory. The shelf stays empty. One isolated write is cheap this way, but writing four bytes and reading one back is five separate trips.
:::
Beware the line boundary: with 8-byte lines, 0x108 starts a new line, so "nearby" is not enough — the addresses must share the same 8-aligned window to be hits.
:::
The grid has four corners. Write-back is forced onto write-allocate (no-allocate would leak the write out immediately and secretly become write-through). Write-through can go either way: with allocate it keeps a shelf copy for future reads while still spilling every write to memory; with no-allocate it streams past the shelf entirely.
:::
The rule: if you'll reuse the neighbourhood soon, reel it in (allocate). If you're just streaming data once and never looking back, skip the shelf (no-allocate) so you don't pollute it with garbage or pay for fetches you'll instantly overwrite.
Reveal-line quick checks: