DEV Community

Shipwright
Shipwright

Posted on Edited on Originally published at shipwrightkit.com

Claude prompt caching: the break-even math and the mistake that turns it off

Prompt caching is the biggest cost lever most Claude apps leave on the table — and the only one that can also make your bill worse. The pricing has a shape people miss: cache reads are cheap, but cache writes carry a surcharge. If you cache things that never get re-read, you are paying extra for the privilege.

Here is the actual math, using the numbers my metering layer bills against.

The four line items on every request

Every Claude request can produce up to four token counts, each at a different rate. This is the pricing function from my model registry — the same one the dashboard, quotas, and billing all read:

/**
 * Cost of a single request in USD, using each model's own cache prices
 * (5-minute cache writes; reads at the model's read rate).
 */
export function costUsd(model: ModelId, usage: TokenUsage): number {
  const m = MODELS[model];
  return (
    (usage.inputTokens * m.inputPerMTok +
      usage.outputTokens * m.outputPerMTok +
      usage.cacheReadTokens * m.cacheReadPerMTok +
      usage.cacheCreationTokens * m.cacheWritePerMTok) /
    1_000_000
  );
}
Enter fullscreen mode Exit fullscreen mode

Two things to internalize:

  • Reads are a 90–95% discount. A cached token bills at a tenth of the normal input rate on most models, and a twentieth on Opus 5.5 ($0.20 per million against $4).
  • Writes are a 25% surcharge. The first time a prefix is cached, those tokens cost 1.25x normal input. Caching is an investment, and investments can lose.

The registry stores the read and write price per model, not as one multiplier. The kit's first version hard-coded 0.1x for reads, which was right for every model it listed until Opus 5.5. Left in place through the upgrade, it would have metered every cached Opus 5.5 token at twice its real price.

Break-even: one re-read pays for the write

Take a 2,000-token system prompt on Claude Opus 5.5 (4 dollars per million input tokens):

  • Uncached, that prefix costs 0.8 cents per request.
  • Writing it to the cache costs 1.0 cent — once.
  • Each subsequent read costs 0.04 cents.

So for N requests inside the cache window: uncached spend is 0.8N cents, cached spend is 1.0 + 0.04(N-1). At N=1 you lost a fifth of a cent. At N=2 you are already 35% ahead. By N=10 the prefix costs 1.36 cents instead of 8.

The general rule falls out of the multipliers: the write premium is 0.25x, each re-read saves 0.9x (0.95x on Opus 5.5) — so a single re-read within the TTL pays for the write more than three times over. In a chat product, where every turn resends the same system prompt and history prefix, that condition is nearly always met. Caching a one-off batch job that never repeats a prefix, though, is a pure 25% surcharge.

The mistake: one dynamic value at the top

Cache matching is a byte-exact prefix match. A single dynamic value early in your system prompt — a timestamp, a request ID, a "today is Wednesday" — changes the bytes, misses the cache, and triggers a fresh 1.25x write on every single request. That is the worst configuration available: you pay the write surcharge every time and never collect a read.

This is why my prompt registry keeps system prompts deterministic, with a comment that has survived every refactor:

/**
 * Keep system prompts deterministic (no timestamps, no per-request IDs):
 * prompt caching is a byte-exact prefix match, and a single dynamic value at
 * the top of the prompt disables caching for everything after it.
 */
Enter fullscreen mode Exit fullscreen mode

Anything per-request — the user name, the date, retrieved context — belongs after the stable prefix, or in the user message, never at the top of the system prompt.

If you cannot see the hit rate, assume zero

My usage table records cache reads and writes as their own columns on every request, because the failure mode here is silent: nothing breaks when caching stops working, the bill just quietly grows. If cache reads are near zero in production while writes keep happening, a dynamic byte crept into your prefix and nobody noticed.

The demo chat's under-the-hood panel has the same split: cache reads and writes get their own lines, labelled with that model's rates, next to the exact cost. They only appear once a prefix is long enough to cache. The demo's own system prompt is about 300 characters, far below any model's minimum, so there you will see plain input tokens. The same discipline as aborting streams when the client disconnects: measure the money path first, then the savings become visible instead of theoretical.

Things that bite

  • The TTL is five minutes. Caching pays inside active sessions and bursty traffic; a user who returns after lunch starts with a fresh write. Do not model savings off your total request count — model them off requests that arrive within minutes of each other.
  • Writes without reads are a surcharge, not a win. Audit any endpoint that caches but rarely repeats a prefix.
  • Very short prompts may not cache at all. Models enforce a minimum cacheable prefix length; below it, your cache_control marker is silently ignored. Anthropic's docs currently list 512 tokens for Opus 5.5, 1,024 for Sonnet 5 and 4,096 for Haiku 4.5. A cheaper model can have a higher minimum, so check it for each model you route to.
  • Neither the ratios nor the absolutes hold across models. On Haiku 4.5 the same 2,000-token prefix does not cache at all, because it is under Haiku's 4,096-token minimum. Uncached it costs 0.2 cents, a quarter of the Opus 5.5 figure. Spend optimization effort where the tokens are expensive, and check that the prefix clears the minimum on the model that actually serves the request.

Shipwright — the Next.js starter kit this blog documents — ships this whole loop wired together: deterministic versioned prompts, cache-aware cost attribution per request in integer micro-USD, and a live panel that shows the math on every message.

Top comments (0)