Tarık Şenkal
Writing
Prefix Caching Economics: When Does the Cache Actually Pay?
Cost 7 min read

Prefix Caching Economics: When Does the Cache Actually Pay?

Cache reads cost 0.1x, writes 1.25x. Break-even is two requests. So why is your cache silently doing nothing? Minimum prefixes, the 20-block rule and the parallel trap.

İsmail Tarık Şenkal
AI developer · Co-founder at HatsonTech

You turned prompt caching on and the bill did not move. This is one of the most common silent failures I see in production, because when caching fails it does not raise an error — it just stops saving you money. This article does two things: first it shows with arithmetic exactly when a cache turns a profit, then it enumerates why yours probably is not working. The break-even math is rarely published anywhere, and that is precisely the part that decides whether caching is worth it.

What prefix caching actually caches

As a language model processes your prompt it produces intermediate representations for every token and stores them as the key-value pairs attention will consume. That store is the KV cache. Prefix caching rests on a simple observation: if the leading, unchanged portion of your prompt is byte-for-byte identical to a previous request, there is no need to recompute it.

Cache matching starts at the very beginning of the prompt and is exact — a single changed space near the top invalidates everything after it.

One rule follows from this and governs everything else: stable content first, variable content last. System instructions, tool definitions and slow-moving document context belong at the front; the user’s current question and any timestamp belong at the end. Putting Current time: 2026-08-13 14:33 at the top of a system prompt is functionally identical to disabling caching.

The break-even arithmetic: two requests or three?

Pricing has three components, quoted as multipliers against the base input price. Cache reads cost roughly 0.1×; cache writes cost 1.25× at the 5-minute TTL and 2× at the 1-hour TTL.

With those three numbers the break-even point is just arithmetic. If you will reuse the same prefix N times, the uncached cost is N × 1.0 and the cached cost is write + (N-1) × 0.1.

5-minute TTL (write 1.25x)
  N=1 :  1.25          vs  1.00   -> loss
  N=2 :  1.25 + 0.10 = 1.35  vs  2.00   -> profit
  N=5 :  1.25 + 0.40 = 1.65  vs  5.00   -> 3.0x saving

1-hour TTL (write 2.00x)
  N=2 :  2.00 + 0.10 = 2.10  vs  2.00   -> still a loss
  N=3 :  2.00 + 0.20 = 2.20  vs  3.00   -> profit
text

So you break even on the second request at the 5-minute TTL and the third at the 1-hour TTL. The practical consequence is that a longer TTL is not automatically better: unless you will hit the prefix at least three times within the window, the longer TTL never repays its write premium.

You do not have to estimate any of this — the API response reports each component separately, so you can compute your true effective multiplier directly:

Tip: Emit this as a production metric. Track the effective multiplier, not the cache hit rate — hit rate can read 90% while short prefixes keep the multiplier above 1.0. The multiplier is what the invoice reflects.

Silent failure: the minimum prefix length

Here is the real problem. A cacheable prefix has a minimum token length, and that minimum varies by model. Worse, it is not monotonic: it is 512 tokens on the newest models, 1,024 on the previous generation, but 4,096 on some mid-generation models.

Fall below that threshold and the request does not fail. No error, no warning. The cache_creation_input_tokens field simply comes back as 0 and you spend months believing caching is on. This is the source of most “we enabled prompt caching and the bill did not change” reports — someone swapped the model and the prefix fell under the threshold.

The fix is not to enable caching but to assert that it engaged, and to do that in a deploy gate rather than by eyeballing it once:

Running this once after the first request is enough. The moment the silent failure becomes loud, the problem stops existing.

The twenty-block rule and agent loops

A single request may define at most four cache breakpoints. When the system looks backwards from a breakpoint for a match, it walks back at most 20 content blocks.

In a plain chat application this never matters. In agent loops it bites immediately: every tool call and every tool result is a separate content block. An agent that makes ten tool calls in a single turn burns through twenty blocks at once. Your breakpoint then cannot reach back to the earlier prefix and the hit disappears silently — exactly where caching would have mattered most, in long agent runs.

  • Move the breakpoint forward: place it at the most recent stable point in accumulated history rather than at the head of the prefix, shortening the distance the 20-block walk must cover.
  • Coalesce tool results: merging five small results into one block slows the block counter by a factor of five.
  • Measure block growth: make blocks-added-per-turn a metric; by the time it approaches 20, the cache is already gone.

The parallel request trap

This one is invisible until you measure it. If you fire N requests with an identical prefix simultaneously, all of them pay full price. The reason is straightforward: the others leave before the first request has finished writing the cache, so there is nothing for them to read.

In a batch job classifying fifty documents against the same system prompt, that means the entire saving evaporates. The correct pattern is a warm-up turn: send one request, wait for the first token, then fan the rest out in parallel.

import asyncio

async def warm_then_fan_out(requests, call):
    """Write the cache with one request, then run the rest in parallel."""
    if not requests:
        return []
    first = await call(requests[0])          # cache is written here
    rest = await asyncio.gather(*(call(r) for r in requests[1:]))
    return [first, *rest]

# WRONG: all at once -> N x full price, zero hits
# results = await asyncio.gather(*(call(r) for r in requests))
python

On a fifty-document batch this one-line difference pulls input cost down by roughly an order of magnitude, and the latency it adds is exactly one request.

Push cache-breakers to the end of the prompt

Most cache-breakers are details nobody thinks of as “content”: a session id buried in the system prompt, a username, a clock, an A/B flag, or a tool list rendered in nondeterministic order. Each of them makes the prefix different on every request.

  • Keep tool definitions in a stable order: if you build them from a dictionary, sort by key — reordering alone breaks the cache.
  • Put timestamps last: if the model needs the date, supply it next to the user message, not at the top of the system prompt.
  • Isolate personalisation: anything user-specific must sit outside the prefix, or the cache fragments once per user.
  • Pin the model version: calling an alias means your cache silently resets whenever the provider rolls the version behind it.

Which leads to the most useful rule here: treat caching as a prompt-architecture decision, not a setting. A cache-friendly prompt is designed stable-first, variable-last; it is not obtained by flipping a flag afterwards.

Key takeaways

  • Reads cost 0.1×, writes 1.25× at 5 minutes and 2× at 1 hour — break-even is the second and third request respectively.
  • The minimum prefix is 512, 1,024 or 4,096 tokens depending on the model; below it caching fails with no error at all.
  • A breakpoint walks back at most 20 content blocks, and agent turns burn that budget fast with tool calls.
  • Parallel requests sharing a prefix all pay full price — send one warm-up request first, then fan out.
Does prompt caching really reduce cost?

Yes, as long as you reuse the prefix, and the effect is large: with a 20,000-token stable prefix the effective input multiplier drops from 1.0 to roughly 0.12. But on one-off requests caching increases cost because of the write premium. The rule is to reuse the prefix at least twice within a 5-minute TTL, or three times within a 1-hour TTL.

Why is my cache not working when I get no error?

Almost always one of two causes: your prefix is below the model's minimum length (512 / 1,024 / 4,096 tokens depending on the model), or the prefix changes on every request. Neither raises an error — cache_creation_input_tokens and cache_read_input_tokens simply stay at zero. Assert on both fields in a deploy gate.

Is the KV cache the same thing as prompt caching?

They are two layers of the same mechanism. The KV cache is the inference-time structure holding per-token key-value representations, used even within a single generation. Prompt caching is the product layer that persists the shared-prefix portion of those representations across requests and prices it at a discount.


Done properly, caching is the largest cost lever available from a single architectural decision. For the other levers see LLM cost optimization, for splitting a prompt into stable and variable layers see context engineering, and for wiring these assertions into a deployment pipeline see MLOps and shipping to production.

İsmail Tarık Şenkal
Author
İsmail Tarık Şenkal

In short: I turn artificial intelligence that comes out of research into products people can rely on day to day.

Related posts