{"version":"https://jsonfeed.org/version/1.1","title":"Om Jee Mishra — Writing","home_page_url":"https://ommishra.tech/blog","feed_url":"https://ommishra.tech/blog/feed.json","description":"Essays and engineering notes on multi-agent AI systems, retrieval and real-time infrastructure.","authors":[{"name":"Om Jee Mishra","url":"https://ommishra.tech"}],"language":"en","items":[{"id":"https://ommishra.tech/blog/prompt-caching-explained","url":"https://ommishra.tech/blog/prompt-caching-explained?ref=jsonfeed","title":"Prompt Caching, Explained: Why Your AI Chatbot Gets Cheaper and Faster the Longer You Talk to It","summary":"AI models have no memory, so you resend the whole conversation on every message and the model re-reads it from scratch. Prompt caching is the fix — reuse the computation for the unchanged prefix and cut API costs 50–90%. Here's what's actually being cached, and the attention math behind why it works.","content_html":"<p>If you've ever built something with ChatGPT, Claude, or Gemini's API, you've probably run into the term \"prompt caching\" in the pricing docs and skimmed past it. It sounds like a backend detail that only matters to infrastructure engineers. It isn't. Understanding it changes how you design AI applications, and it can cut your API bill by 50–90%.</p>\n<p>This article explains what prompt caching actually is, why it exists, and how it works — with real examples, no assumed AI background required. Toward the end, we'll go one level deeper into the actual computation, for readers who want the engineering picture too.</p>\n<hr />\n<h2 id=\"start-with-a-problem-youve-probably-felt\"><a class=\"anchor\" href=\"#start-with-a-problem-youve-probably-felt\" aria-hidden=\"true\">#</a>Start with a problem you've probably felt</h2>\n<p>Imagine you're building a customer support chatbot. Every time a user sends a message, you don't just send that one message to the AI model — you send the <strong>entire conversation so far</strong>, plus a system prompt describing how the bot should behave, plus maybe a list of tools it can use.</p>\n<p>So by message 10 of a conversation, your request to the AI looks something like this:</p>\n<pre class=\"shiki github-light\" style=\"background-color:#fff;color:#24292e\" tabindex=\"0\"><code><span class=\"line\"><span>System prompt (500 words: \"You are a support agent for Acme Corp...\")</span></span>\n<span class=\"line\"><span>+ Tool definitions (300 words: refund_order, check_shipping, etc.)</span></span>\n<span class=\"line\"><span>+ Message 1 (user)</span></span>\n<span class=\"line\"><span>+ Message 1 (assistant reply)</span></span>\n<span class=\"line\"><span>+ Message 2 (user)</span></span>\n<span class=\"line\"><span>+ Message 2 (assistant reply)</span></span>\n<span class=\"line\"><span>...</span></span>\n<span class=\"line\"><span>+ Message 10 (user) ← the only actually new part</span></span></code></pre><p>Here's the catch: <strong>AI models have no memory.</strong> Every single request is treated as brand new. The model doesn't \"remember\" message 9 — you have to physically resend it, every time, forever, for the conversation to make sense. This means that by message 10, you're re-sending (and the model is re-reading) 9 previous exchanges just to add one new line.</p>\n<p>Now imagine 10,000 users doing this simultaneously with your chatbot, all sharing the same 500-word system prompt and the same tool definitions. That's a <em>lot</em> of repeated reading happening constantly, for text that never changes.</p>\n<p>This is exactly the waste that prompt caching is designed to eliminate.</p>\n<hr />\n<h2 id=\"the-analogy-that-makes-it-click\"><a class=\"anchor\" href=\"#the-analogy-that-makes-it-click\" aria-hidden=\"true\">#</a>The analogy that makes it click</h2>\n<p>Think about how you'd study for an exam using a textbook.</p>\n<p>The first time you read Chapter 1, it takes real effort — you're parsing every sentence, building understanding from scratch. But if someone asks you a follow-up question about Chapter 1 five minutes later, you don't re-read the whole chapter again. You already processed it. It's fresh in your head. You just recall it instantly and answer.</p>\n<p>Prompt caching gives the AI model a similar shortcut. If you send it the same chunk of text (like your system prompt) that it has <em>very recently</em> already processed, it doesn't need to \"re-read and re-understand\" that chunk from zero. It reuses the work it already did, and only spends real effort on the genuinely new part of your message.</p>\n<p>The important nuance: <strong>the model isn't \"remembering\" in a human sense.</strong> It's not storing an opinion or a memory of the conversation. What's being reused is purely the <em>computational effort</em> of processing that specific block of text — like reusing a highlighted, annotated textbook instead of a blank one.</p>\n<hr />\n<h2 id=\"so-whats-actually-being-cached\"><a class=\"anchor\" href=\"#so-whats-actually-being-cached\" aria-hidden=\"true\">#</a>So what's actually being \"cached\"?</h2>\n<p>When an AI model reads your prompt, it doesn't process it all at once as a blob. It processes it token by token (a token is roughly a word or word-fragment), and for every token, it performs a chunk of internal math to figure out \"how does this word relate to every other word around it.\" This step is called <strong>attention</strong>, and it's genuinely the most expensive part of running these models.</p>\n<p>Here's the key fact that makes caching possible: for any block of text, once the model has done this \"relate this word to its surroundings\" math, <strong>the result never changes</strong> — as long as nothing before that block changes. If your system prompt is always identical, the model's internal understanding of that system prompt is always identical too. So instead of redoing that expensive math every single time, the AI provider's servers can save the <em>result</em> of that math the first time, and simply reuse it on every future request — as long as the text is the same.</p>\n<p>That saved intermediate result is what's technically called the <strong>KV cache</strong> (short for \"Key-Value cache\" — a reference to the internal math structure, which we won't need to unpack for this article, but it's worth knowing the name if you read pricing docs).</p>\n<p>Two important things about this cache:</p>\n<ol>\n<li><p><strong>It's prefix-based.</strong> The cached part has to be the <em>beginning</em> of your prompt, matching exactly, word for word. If you change even one sentence in your system prompt, everything from that point onward has to be recomputed — the cache only helps for the part that's identical.</p>\n</li>\n<li><p><strong>It's temporary.</strong> Providers don't keep this saved forever. Typically it lives for about 5–10 minutes of inactivity, though some providers now offer longer retention (up to an hour or more) for an extra cost. If nobody hits that exact same prefix again within that window, the cache quietly expires and gets recomputed fresh next time.</p>\n</li>\n</ol>\n<hr />\n<h2 id=\"okay-but-what-is-the-model-actually-doing-with-that-text-the-deeper-technical-la\"><a class=\"anchor\" href=\"#okay-but-what-is-the-model-actually-doing-with-that-text-the-deeper-technical-la\" aria-hidden=\"true\">#</a>Okay, but what is the model actually <em>doing</em> with that text? (The deeper technical layer)</h2>\n<p>This section goes a level deeper into the mechanics. You don't need it to use prompt caching effectively, but it makes the \"why\" click in a way that sticks.</p>\n<h3 id=\"every-word-gets-three-roles-assigned-to-it\"><a class=\"anchor\" href=\"#every-word-gets-three-roles-assigned-to-it\" aria-hidden=\"true\">#</a>Every word gets three \"roles\" assigned to it</h3>\n<p>When the model reads a sentence, it doesn't just see plain words. For every single word (technically, \"token\"), the model computes three different mathematical fingerprints of it, called <strong>Query</strong>, <strong>Key</strong>, and <strong>Value</strong> — Q, K, V for short.</p>\n<p>Here's what that looks like as a diagram, for one single word:</p>\n<pre class=\"shiki github-light\" style=\"background-color:#fff;color:#24292e\" tabindex=\"0\"><code><span class=\"line\"><span>                     Token: \"cat\"</span></span>\n<span class=\"line\"><span>                          |</span></span>\n<span class=\"line\"><span>                   [ embedding vector ]</span></span>\n<span class=\"line\"><span>                          |</span></span>\n<span class=\"line\"><span>        ┌─────────────────┼─────────────────┐</span></span>\n<span class=\"line\"><span>        |                 |                 |</span></span>\n<span class=\"line\"><span>   × W_Query          × W_Key           × W_Value</span></span>\n<span class=\"line\"><span>        |                 |                 |</span></span>\n<span class=\"line\"><span>        ▼                 ▼                 ▼</span></span>\n<span class=\"line\"><span>   Query(\"cat\")       Key(\"cat\")        Value(\"cat\")</span></span>\n<span class=\"line\"><span>   \"what am I          \"what do I        \"what info do</span></span>\n<span class=\"line\"><span>    looking for?\"       have to offer?\"   I actually hand</span></span>\n<span class=\"line\"><span>                                           over if picked?\"</span></span></code></pre><p>Every single token in your prompt goes through this same three-way split — one word in, three different-purpose vectors out. None of them are the \"real\" version of the word; they're three different lenses on it, each built for a different job in the next step.</p>\n<p>A relatable way to picture this: imagine a room full of people at a networking event, and every person wears three name tags:</p>\n<ul>\n<li><strong>Query tag</strong> — \"Here's what I'm currently looking for in this conversation.\"</li>\n<li><strong>Key tag</strong> — \"Here's what I have to offer, as a topic someone else might be looking for.\"</li>\n<li><strong>Value tag</strong> — \"Here's the actual information I'll share if someone finds me relevant.\"</li>\n</ul>\n<p>When it's your turn to speak, you look around the room holding up your Query tag, comparing it against everyone else's Key tag, to figure out <em>who in the room is actually relevant to what you're trying to say right now</em>. Then, you pull the actual content — the Value — from the people who matched best, and blend it into what you say next.</p>\n<p>That's, quite literally, what happens inside every layer of the model, for every word, all at once. Take the sentence <strong>\"The cat sat on the mat because it was tired.\"</strong> When the model processes the word \"it,\" its Query is essentially asking \"who am I referring to?\" It compares that Query against the Keys of every earlier word — \"cat,\" \"sat,\" \"mat,\" \"tired\" — and finds that \"cat\" is the strongest match. So it pulls \"cat's\" Value into its understanding of \"it.\" This comparison-and-blend process, repeated for every word against every other word, is what people mean when they say a model is \"paying attention\" to context. It's not a metaphor — it's the literal mechanism, running as matrix multiplication under the hood.</p>\n<p>Here's that same example as a diagram — the word \"it\" comparing its Query against every earlier word's Key, then blending in their Values by how well each one matched:</p>\n<pre class=\"shiki github-light\" style=\"background-color:#fff;color:#24292e\" tabindex=\"0\"><code><span class=\"line\"><span>Sentence so far:   \"The  cat  sat  on  the  mat  because  it ...\"</span></span>\n<span class=\"line\"><span>                                                            |</span></span>\n<span class=\"line\"><span>                                                     Query(\"it\")</span></span>\n<span class=\"line\"><span>                                                            |</span></span>\n<span class=\"line\"><span>              ┌───────────────┬───────────────┬────────────┴──────────────┐</span></span>\n<span class=\"line\"><span>              |                |               |                          |</span></span>\n<span class=\"line\"><span>        Key(\"cat\")       Key(\"sat\")      Key(\"mat\")                Key(\"tired\")</span></span>\n<span class=\"line\"><span>              |                |               |                          |</span></span>\n<span class=\"line\"><span>       match: 0.82       match: 0.06     match: 0.05                match: 0.07</span></span>\n<span class=\"line\"><span>        (strong!)          (weak)          (weak)                     (weak)</span></span>\n<span class=\"line\"><span>              |                |               |                          |</span></span>\n<span class=\"line\"><span>              ▼                ▼               ▼                          ▼</span></span>\n<span class=\"line\"><span>        Value(\"cat\") ×0.82 + Value(\"sat\")×0.06 + Value(\"mat\")×0.05 + Value(\"tired\")×0.07</span></span>\n<span class=\"line\"><span>              └────────────────────────────┬────────────────────────────────┘</span></span>\n<span class=\"line\"><span>                                            ▼</span></span>\n<span class=\"line\"><span>                          New meaning of \"it\" = mostly \"cat\",</span></span>\n<span class=\"line\"><span>                          a little bit of everything else</span></span></code></pre><p>The percentages (0.82, 0.06, 0.05, 0.07 — always adding up to 1.0) come from a step called <strong>softmax</strong>, which just turns raw \"how well do these match\" scores into clean, comparable weights. The point of the diagram: \"it\" ends up meaning \"mostly cat\" not because the model looked up a dictionary definition, but because \"cat's\" Key was the best match for \"it's\" Query, so \"cat's\" Value dominated the blend.</p>\n<h3 id=\"why-this-gets-expensive-as-conversations-grow\"><a class=\"anchor\" href=\"#why-this-gets-expensive-as-conversations-grow\" aria-hidden=\"true\">#</a>Why this gets expensive as conversations grow</h3>\n<p>Here's the relatable part: imagine a party where every new guest who arrives has to shake hands and exchange a \"who are you, what do you know\" conversation with <strong>every single person already in the room</strong>.</p>\n<ul>\n<li>5 people at the party → 10 handshakes needed to fully connect everyone.</li>\n<li>50 people → over 1,200 handshakes.</li>\n<li>500 people → over 124,000 handshakes.</li>\n</ul>\n<p>That's roughly how attention scales if you did it the naive way for every new word generated: each new word has to compare itself against every previous word, and as the conversation grows, this blows up <strong>quadratically</strong> — technically written as O(n²), meaning if the conversation doubles in length, the work more than doubles (it roughly quadruples). This is the mathematical reason very long conversations start to feel slower and more expensive per message — there's simply more \"comparing against everyone in the room\" happening.</p>\n<p>Here's what that growth looks like laid out side by side, guest-by-guest, with and without a saved guest list:</p>\n<pre class=\"shiki github-light\" style=\"background-color:#fff;color:#24292e\" tabindex=\"0\"><code><span class=\"line\"><span>WITHOUT caching (everyone re-introduces themselves to the whole room, every time)</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span> Guest count:     5          50           500</span></span>\n<span class=\"line\"><span> Handshakes:      10        1,225       124,750     ← grows as n²  (explodes)</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span>WITH KV cache (only the newest guest introduces themselves; everyone else's</span></span>\n<span class=\"line\"><span>                tag is already written down on the list)</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span> Guest count:     5          50           500</span></span>\n<span class=\"line\"><span> New handshakes:  4          49           499        ← grows as n   (steady, linear)</span></span>\n<span class=\"line\"><span>                  ▲           ▲            ▲</span></span>\n<span class=\"line\"><span>            (only handshakes involving the newest arrival)</span></span></code></pre><p>The gap between those two rows — 124,750 vs. 499 at 500 guests — is the entire reason this optimization matters at real-world scale. It's not a minor tweak; it's the difference between a conversation that gets dramatically slower the longer it runs, and one that stays roughly steady per new message.</p>\n<h3 id=\"why-k-and-v-specifically-get-saved-and-why-that-fixes-the-problem\"><a class=\"anchor\" href=\"#why-k-and-v-specifically-get-saved-and-why-that-fixes-the-problem\" aria-hidden=\"true\">#</a>Why K and V specifically get \"saved,\" and why that fixes the problem</h3>\n<p>Now here's the trick that makes caching possible, and it comes from one simple fact: <strong>once a word's Key and Value tags are made, they never change</strong>, no matter what gets said afterward. \"Cat's\" Key and Value tag, computed the moment \"cat\" was said, stay exactly the same whether the sentence ends there or continues for another 500 words. Nothing that happens <em>later</em> in the conversation can retroactively change what \"cat\" <em>is</em>.</p>\n<p>Query, on the other hand, is different — it's only relevant for the person speaking <em>right now</em>, in the current moment, and gets thrown away once that turn is done.</p>\n<p>So the engineering shortcut becomes obvious: instead of making everyone re-introduce themselves (recompute Key and Value) every single time a new person speaks, you keep a running guest list with everyone's Key/Value tags already written down. When a new word/token arrives, it only has to write its own tag once, then it can instantly \"read\" everyone else's tags off the list, without asking them to reintroduce themselves. That guest list is the <strong>KV cache</strong>. It's the reason the actual, real-world scaling of generating each new word is closer to <strong>O(n)</strong> in practice — mostly linear, one new lookup per existing guest, not a repeated round of handshakes for the entire room every time.</p>\n<p>Prompt caching (the API feature we've been discussing) is this exact same idea, just stretched across separate <em>requests</em> instead of within one ongoing generation. If your system prompt is the same 500-word block every time, its Key and Value tags never need to be rewritten — the server keeps that \"guest list\" saved for a few minutes and simply hands it back the moment it sees the same block walk in the door again.</p>\n<pre class=\"shiki github-light\" style=\"background-color:#fff;color:#24292e\" tabindex=\"0\"><code><span class=\"line\"><span>REQUEST 1  (first message of the conversation)</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span>  [ System prompt ][ Tools ][ Message 1 ]</span></span>\n<span class=\"line\"><span>        |               |          |</span></span>\n<span class=\"line\"><span>        ▼               ▼          ▼</span></span>\n<span class=\"line\"><span>   compute K/V     compute K/V   compute K/V     ← full cost, everything is new</span></span>\n<span class=\"line\"><span>        └───────────────┴──────────┘</span></span>\n<span class=\"line\"><span>                    |</span></span>\n<span class=\"line\"><span>             (saved to cache, ~5-10 min)</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span>REQUEST 2  (next message, moments later)</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span>  [ System prompt ][ Tools ][ Message 1 ][ Reply 1 ][ Message 2 ]</span></span>\n<span class=\"line\"><span>        |               |          |           |            |</span></span>\n<span class=\"line\"><span>        ▼               ▼          ▼           ▼            ▼</span></span>\n<span class=\"line\"><span>   CACHE HIT       CACHE HIT   CACHE HIT   CACHE HIT   compute K/V</span></span>\n<span class=\"line\"><span>   (reused,          (reused,    (reused,    (reused,      ← only the new</span></span>\n<span class=\"line\"><span>    ~10% cost)        cheap)      cheap)      cheap)          part is paid</span></span>\n<span class=\"line\"><span>                                                                for in full</span></span>\n<span class=\"line\"><span>        └───────────────┴──────────┴───────────┴────────────┘</span></span>\n<span class=\"line\"><span>                    |</span></span>\n<span class=\"line\"><span>         same list, just one new guest added,</span></span>\n<span class=\"line\"><span>              saved again for next time</span></span></code></pre><p>Everything shaded as \"reused\" above didn't need to be re-read or re-understood by the model — it was pulled straight from what got saved a moment ago. Only the newest sliver of the conversation, at the far right, costs full price. That's the entire mechanism, just repeated every time you send a new message.</p>\n<h3 id=\"putting-it-together-with-a-number\"><a class=\"anchor\" href=\"#putting-it-together-with-a-number\" aria-hidden=\"true\">#</a>Putting it together with a number</h3>\n<p>Say your model has, roughly, dozens of these Q/K/V comparisons happening per layer, and dozens of layers stacked on top of each other (this is genuinely how large models are structured — 30 to 80+ layers is common). Every one of those layers is running this \"compare Query against everyone's Key, blend in everyone's Value\" process, for every single token. Multiply that across a 3,000-token system prompt, and you can see why re-doing it on every single chat message — for every single user — becomes a real, measurable cost. And why <em>not</em> re-doing it, via caching, is worth a 90% price cut from providers: they're genuinely saving that much repeated computation.</p>\n<hr />\n<h2 id=\"a-concrete-beforeafter-example\"><a class=\"anchor\" href=\"#a-concrete-beforeafter-example\" aria-hidden=\"true\">#</a>A concrete before/after example</h2>\n<p>Let's say your chatbot has:</p>\n<ul>\n<li>A 2,000-token system prompt describing the bot's personality and rules</li>\n<li>A 1,000-token block of tool definitions</li>\n<li>A growing conversation history</li>\n</ul>\n<p><strong>Without caching</strong>, every single message the user sends forces the model to re-read all 3,000+ tokens of setup, plus the whole history, before it can even start thinking about the new message.</p>\n<p><strong>With caching</strong>, the first message pays that full cost once. Every message after that only pays for:</p>\n<ul>\n<li>The new user message</li>\n<li>Reusing the already-processed system prompt + tools + prior history (near-instant, much cheaper)</li>\n</ul>\n<p>Providers price this difference dramatically. As an illustrative example, one major provider charges roughly a small premium the <em>first</em> time a chunk gets cached (since storing it costs them something), but then charges around <strong>90% less</strong> for every subsequent request that reuses it. If your app has any repeated structure at all — a shared system prompt across users, a long document being asked about repeatedly, a multi-turn conversation — the savings compound fast.</p>\n<p>This is also why caching improves <strong>speed</strong>, not just cost. Skipping the re-processing step means the model can start generating its answer sooner — providers report cutting time-to-first-word by up to 80%+ on long prompts.</p>\n<hr />\n<h2 id=\"where-this-really-pays-off-real-scenarios\"><a class=\"anchor\" href=\"#where-this-really-pays-off-real-scenarios\" aria-hidden=\"true\">#</a>Where this really pays off (real scenarios)</h2>\n<ul>\n<li><strong>Customer support / chat assistants</strong> — same system prompt and tool list on every single user turn, across every single user. Massive repeated prefix.</li>\n<li><strong>\"Chat with your PDF\" tools</strong> — you paste in a 20-page document once, then ask multiple questions about it. The document text becomes the cached prefix; only your questions change.</li>\n<li><strong>Coding assistants</strong> — a large codebase or set of instructions gets reused across many small follow-up edits.</li>\n<li><strong>Agents that call tools repeatedly</strong> — every tool definition gets resent on every step of a multi-step task; caching keeps that from being re-billed every step.</li>\n</ul>\n<p>Where it does <strong>not</strong> help: one-off requests, prompts that are different every time with no shared prefix, or conversations where the system prompt/instructions change on every single call.</p>\n<hr />\n<h2 id=\"common-misconception-worth-stating-clearly\"><a class=\"anchor\" href=\"#common-misconception-worth-stating-clearly\" aria-hidden=\"true\">#</a>Common misconception, worth stating clearly</h2>\n<p>People sometimes assume prompt caching means the model is becoming \"stateful\" — that it's starting to genuinely retain memory of you between sessions. It isn't. This is purely a performance optimization sitting underneath the model, invisible to it. The model's actual behavior, its answer, is (in principle) identical whether the cache was used or not — caching only changes <em>how fast and how cheaply</em> that identical answer gets produced. You still have to manage and resend your own conversation history and context every time; caching doesn't remove that responsibility, it just makes the resending cheaper when the resent part is unchanged.</p>\n<p>It's also worth knowing that this cache is kept private and isolated — your organization's cached data isn't shared with, or accessible to, other customers, even if by coincidence they happen to use a very similar prompt.</p>\n<hr />\n<h2 id=\"for-the-applied-ai-engineers-reading-this-a-bit-more-depth\"><a class=\"anchor\" href=\"#for-the-applied-ai-engineers-reading-this-a-bit-more-depth\" aria-hidden=\"true\">#</a>For the applied AI engineers reading this: a bit more depth</h2>\n<p>If you're actually building with these APIs, here's what matters practically:</p>\n<p><strong>It's prefix-order sensitive.</strong> Put the stuff that never changes <em>first</em> (system prompt, tool definitions, static reference documents), and put the stuff that changes every turn (the newest user message) <em>last</em>. If you interleave static and dynamic content, you break the matching prefix and lose the cache benefit for everything after the change.</p>\n<p><strong>There's a minimum size.</strong> Most providers require a cached block to be at least ~1,000 tokens before it's worth caching — trying to cache a 50-token system prompt won't do much.</p>\n<p><strong>Explicit vs automatic caching varies by provider.</strong> Some providers cache automatically behind the scenes with no configuration needed. Others require you to explicitly mark where the cacheable portion of your prompt ends, using a parameter in your API call. Check your specific provider's docs — this changes how much control (and how much savings) you actually get.</p>\n<p><strong>Cache lifetime is short by default.</strong> If your app has bursty, spaced-out traffic (e.g., a user comes back every 20 minutes), you may fall outside the default cache window and pay full price again. Some providers let you pay a bit more for extended retention if this matters for your use case.</p>\n<p><strong>It composes with your own conversation-loop design.</strong> If you're manually managing multi-turn context (appending history, tool results, etc. and re-sending it), structuring that history to stay as a stable, append-only prefix (rather than editing/reordering past messages) is what keeps cache hits high turn after turn.</p>\n<hr />\n<h2 id=\"the-one-sentence-summary\"><a class=\"anchor\" href=\"#the-one-sentence-summary\" aria-hidden=\"true\">#</a>The one-sentence summary</h2>\n<p>Prompt caching is the AI provider saying: \"If you send me the same beginning again, I won't waste time and money re-understanding it from scratch — I'll just remember the work I already did, for a little while, and pick up right where that left off.\"</p>\n<p>It doesn't make the model smarter, and it doesn't give it real memory — it just makes repeated, structured conversations dramatically cheaper and faster, which is exactly the shape of workload most real AI products actually have.</p>\n","content_text":"If you've ever built something with ChatGPT, Claude, or Gemini's API, you've probably run into the term \"prompt caching\" in the pricing docs and skimmed past it. It sounds like a backend detail that only matters to infrastructure engineers. It isn't. Understanding it changes how you design AI applications, and it can cut your API bill by 50–90%.\n\nThis article explains what prompt caching actually is, why it exists, and how it works — with real examples, no assumed AI background required. Toward the end, we'll go one level deeper into the actual computation, for readers who want the engineering picture too.\n\n---\n\n## Start with a problem you've probably felt\n\nImagine you're building a customer support chatbot. Every time a user sends a message, you don't just send that one message to the AI model — you send the **entire conversation so far**, plus a system prompt describing how the bot should behave, plus maybe a list of tools it can use.\n\nSo by message 10 of a conversation, your request to the AI looks something like this:\n\n```\nSystem prompt (500 words: \"You are a support agent for Acme Corp...\")\n+ Tool definitions (300 words: refund_order, check_shipping, etc.)\n+ Message 1 (user)\n+ Message 1 (assistant reply)\n+ Message 2 (user)\n+ Message 2 (assistant reply)\n...\n+ Message 10 (user) ← the only actually new part\n```\n\nHere's the catch: **AI models have no memory.** Every single request is treated as brand new. The model doesn't \"remember\" message 9 — you have to physically resend it, every time, forever, for the conversation to make sense. This means that by message 10, you're re-sending (and the model is re-reading) 9 previous exchanges just to add one new line.\n\nNow imagine 10,000 users doing this simultaneously with your chatbot, all sharing the same 500-word system prompt and the same tool definitions. That's a _lot_ of repeated reading happening constantly, for text that never changes.\n\nThis is exactly the waste that prompt caching is designed to eliminate.\n\n---\n\n## The analogy that makes it click\n\nThink about how you'd study for an exam using a textbook.\n\nThe first time you read Chapter 1, it takes real effort — you're parsing every sentence, building understanding from scratch. But if someone asks you a follow-up question about Chapter 1 five minutes later, you don't re-read the whole chapter again. You already processed it. It's fresh in your head. You just recall it instantly and answer.\n\nPrompt caching gives the AI model a similar shortcut. If you send it the same chunk of text (like your system prompt) that it has _very recently_ already processed, it doesn't need to \"re-read and re-understand\" that chunk from zero. It reuses the work it already did, and only spends real effort on the genuinely new part of your message.\n\nThe important nuance: **the model isn't \"remembering\" in a human sense.** It's not storing an opinion or a memory of the conversation. What's being reused is purely the _computational effort_ of processing that specific block of text — like reusing a highlighted, annotated textbook instead of a blank one.\n\n---\n\n## So what's actually being \"cached\"?\n\nWhen an AI model reads your prompt, it doesn't process it all at once as a blob. It processes it token by token (a token is roughly a word or word-fragment), and for every token, it performs a chunk of internal math to figure out \"how does this word relate to every other word around it.\" This step is called **attention**, and it's genuinely the most expensive part of running these models.\n\nHere's the key fact that makes caching possible: for any block of text, once the model has done this \"relate this word to its surroundings\" math, **the result never changes** — as long as nothing before that block changes. If your system prompt is always identical, the model's internal understanding of that system prompt is always identical too. So instead of redoing that expensive math every single time, the AI provider's servers can save the _result_ of that math the first time, and simply reuse it on every future request — as long as the text is the same.\n\nThat saved intermediate result is what's technically called the **KV cache** (short for \"Key-Value cache\" — a reference to the internal math structure, which we won't need to unpack for this article, but it's worth knowing the name if you read pricing docs).\n\nTwo important things about this cache:\n\n1. **It's prefix-based.** The cached part has to be the _beginning_ of your prompt, matching exactly, word for word. If you change even one sentence in your system prompt, everything from that point onward has to be recomputed — the cache only helps for the part that's identical.\n\n2. **It's temporary.** Providers don't keep this saved forever. Typically it lives for about 5–10 minutes of inactivity, though some providers now offer longer retention (up to an hour or more) for an extra cost. If nobody hits that exact same prefix again within that window, the cache quietly expires and gets recomputed fresh next time.\n\n---\n\n## Okay, but what is the model actually _doing_ with that text? (The deeper technical layer)\n\nThis section goes a level deeper into the mechanics. You don't need it to use prompt caching effectively, but it makes the \"why\" click in a way that sticks.\n\n### Every word gets three \"roles\" assigned to it\n\nWhen the model reads a sentence, it doesn't just see plain words. For every single word (technically, \"token\"), the model computes three different mathematical fingerprints of it, called **Query**, **Key**, and **Value** — Q, K, V for short.\n\nHere's what that looks like as a diagram, for one single word:\n\n```\n                     Token: \"cat\"\n                          |\n                   [ embedding vector ]\n                          |\n        ┌─────────────────┼─────────────────┐\n        |                 |                 |\n   × W_Query          × W_Key           × W_Value\n        |                 |                 |\n        ▼                 ▼                 ▼\n   Query(\"cat\")       Key(\"cat\")        Value(\"cat\")\n   \"what am I          \"what do I        \"what info do\n    looking for?\"       have to offer?\"   I actually hand\n                                           over if picked?\"\n```\n\nEvery single token in your prompt goes through this same three-way split — one word in, three different-purpose vectors out. None of them are the \"real\" version of the word; they're three different lenses on it, each built for a different job in the next step.\n\nA relatable way to picture this: imagine a room full of people at a networking event, and every person wears three name tags:\n\n- **Query tag** — \"Here's what I'm currently looking for in this conversation.\"\n- **Key tag** — \"Here's what I have to offer, as a topic someone else might be looking for.\"\n- **Value tag** — \"Here's the actual information I'll share if someone finds me relevant.\"\n\nWhen it's your turn to speak, you look around the room holding up your Query tag, comparing it against everyone else's Key tag, to figure out _who in the room is actually relevant to what you're trying to say right now_. Then, you pull the actual content — the Value — from the people who matched best, and blend it into what you say next.\n\nThat's, quite literally, what happens inside every layer of the model, for every word, all at once. Take the sentence **\"The cat sat on the mat because it was tired.\"** When the model processes the word \"it,\" its Query is essentially asking \"who am I referring to?\" It compares that Query against the Keys of every earlier word — \"cat,\" \"sat,\" \"mat,\" \"tired\" — and finds that \"cat\" is the strongest match. So it pulls \"cat's\" Value into its understanding of \"it.\" This comparison-and-blend process, repeated for every word against every other word, is what people mean when they say a model is \"paying attention\" to context. It's not a metaphor — it's the literal mechanism, running as matrix multiplication under the hood.\n\nHere's that same example as a diagram — the word \"it\" comparing its Query against every earlier word's Key, then blending in their Values by how well each one matched:\n\n```\nSentence so far:   \"The  cat  sat  on  the  mat  because  it ...\"\n                                                            |\n                                                     Query(\"it\")\n                                                            |\n              ┌───────────────┬───────────────┬────────────┴──────────────┐\n              |                |               |                          |\n        Key(\"cat\")       Key(\"sat\")      Key(\"mat\")                Key(\"tired\")\n              |                |               |                          |\n       match: 0.82       match: 0.06     match: 0.05                match: 0.07\n        (strong!)          (weak)          (weak)                     (weak)\n              |                |               |                          |\n              ▼                ▼               ▼                          ▼\n        Value(\"cat\") ×0.82 + Value(\"sat\")×0.06 + Value(\"mat\")×0.05 + Value(\"tired\")×0.07\n              └────────────────────────────┬────────────────────────────────┘\n                                            ▼\n                          New meaning of \"it\" = mostly \"cat\",\n                          a little bit of everything else\n```\n\nThe percentages (0.82, 0.06, 0.05, 0.07 — always adding up to 1.0) come from a step called **softmax**, which just turns raw \"how well do these match\" scores into clean, comparable weights. The point of the diagram: \"it\" ends up meaning \"mostly cat\" not because the model looked up a dictionary definition, but because \"cat's\" Key was the best match for \"it's\" Query, so \"cat's\" Value dominated the blend.\n\n### Why this gets expensive as conversations grow\n\nHere's the relatable part: imagine a party where every new guest who arrives has to shake hands and exchange a \"who are you, what do you know\" conversation with **every single person already in the room**.\n\n- 5 people at the party → 10 handshakes needed to fully connect everyone.\n- 50 people → over 1,200 handshakes.\n- 500 people → over 124,000 handshakes.\n\nThat's roughly how attention scales if you did it the naive way for every new word generated: each new word has to compare itself against every previous word, and as the conversation grows, this blows up **quadratically** — technically written as O(n²), meaning if the conversation doubles in length, the work more than doubles (it roughly quadruples). This is the mathematical reason very long conversations start to feel slower and more expensive per message — there's simply more \"comparing against everyone in the room\" happening.\n\nHere's what that growth looks like laid out side by side, guest-by-guest, with and without a saved guest list:\n\n```\nWITHOUT caching (everyone re-introduces themselves to the whole room, every time)\n\n Guest count:     5          50           500\n Handshakes:      10        1,225       124,750     ← grows as n²  (explodes)\n\n\nWITH KV cache (only the newest guest introduces themselves; everyone else's\n                tag is already written down on the list)\n\n Guest count:     5          50           500\n New handshakes:  4          49           499        ← grows as n   (steady, linear)\n                  ▲           ▲            ▲\n            (only handshakes involving the newest arrival)\n```\n\nThe gap between those two rows — 124,750 vs. 499 at 500 guests — is the entire reason this optimization matters at real-world scale. It's not a minor tweak; it's the difference between a conversation that gets dramatically slower the longer it runs, and one that stays roughly steady per new message.\n\n### Why K and V specifically get \"saved,\" and why that fixes the problem\n\nNow here's the trick that makes caching possible, and it comes from one simple fact: **once a word's Key and Value tags are made, they never change**, no matter what gets said afterward. \"Cat's\" Key and Value tag, computed the moment \"cat\" was said, stay exactly the same whether the sentence ends there or continues for another 500 words. Nothing that happens _later_ in the conversation can retroactively change what \"cat\" _is_.\n\nQuery, on the other hand, is different — it's only relevant for the person speaking _right now_, in the current moment, and gets thrown away once that turn is done.\n\nSo the engineering shortcut becomes obvious: instead of making everyone re-introduce themselves (recompute Key and Value) every single time a new person speaks, you keep a running guest list with everyone's Key/Value tags already written down. When a new word/token arrives, it only has to write its own tag once, then it can instantly \"read\" everyone else's tags off the list, without asking them to reintroduce themselves. That guest list is the **KV cache**. It's the reason the actual, real-world scaling of generating each new word is closer to **O(n)** in practice — mostly linear, one new lookup per existing guest, not a repeated round of handshakes for the entire room every time.\n\nPrompt caching (the API feature we've been discussing) is this exact same idea, just stretched across separate _requests_ instead of within one ongoing generation. If your system prompt is the same 500-word block every time, its Key and Value tags never need to be rewritten — the server keeps that \"guest list\" saved for a few minutes and simply hands it back the moment it sees the same block walk in the door again.\n\n```\nREQUEST 1  (first message of the conversation)\n\n  [ System prompt ][ Tools ][ Message 1 ]\n        |               |          |\n        ▼               ▼          ▼\n   compute K/V     compute K/V   compute K/V     ← full cost, everything is new\n        └───────────────┴──────────┘\n                    |\n             (saved to cache, ~5-10 min)\n\n\nREQUEST 2  (next message, moments later)\n\n  [ System prompt ][ Tools ][ Message 1 ][ Reply 1 ][ Message 2 ]\n        |               |          |           |            |\n        ▼               ▼          ▼           ▼            ▼\n   CACHE HIT       CACHE HIT   CACHE HIT   CACHE HIT   compute K/V\n   (reused,          (reused,    (reused,    (reused,      ← only the new\n    ~10% cost)        cheap)      cheap)      cheap)          part is paid\n                                                                for in full\n        └───────────────┴──────────┴───────────┴────────────┘\n                    |\n         same list, just one new guest added,\n              saved again for next time\n```\n\nEverything shaded as \"reused\" above didn't need to be re-read or re-understood by the model — it was pulled straight from what got saved a moment ago. Only the newest sliver of the conversation, at the far right, costs full price. That's the entire mechanism, just repeated every time you send a new message.\n\n### Putting it together with a number\n\nSay your model has, roughly, dozens of these Q/K/V comparisons happening per layer, and dozens of layers stacked on top of each other (this is genuinely how large models are structured — 30 to 80+ layers is common). Every one of those layers is running this \"compare Query against everyone's Key, blend in everyone's Value\" process, for every single token. Multiply that across a 3,000-token system prompt, and you can see why re-doing it on every single chat message — for every single user — becomes a real, measurable cost. And why _not_ re-doing it, via caching, is worth a 90% price cut from providers: they're genuinely saving that much repeated computation.\n\n---\n\n## A concrete before/after example\n\nLet's say your chatbot has:\n\n- A 2,000-token system prompt describing the bot's personality and rules\n- A 1,000-token block of tool definitions\n- A growing conversation history\n\n**Without caching**, every single message the user sends forces the model to re-read all 3,000+ tokens of setup, plus the whole history, before it can even start thinking about the new message.\n\n**With caching**, the first message pays that full cost once. Every message after that only pays for:\n\n- The new user message\n- Reusing the already-processed system prompt + tools + prior history (near-instant, much cheaper)\n\nProviders price this difference dramatically. As an illustrative example, one major provider charges roughly a small premium the _first_ time a chunk gets cached (since storing it costs them something), but then charges around **90% less** for every subsequent request that reuses it. If your app has any repeated structure at all — a shared system prompt across users, a long document being asked about repeatedly, a multi-turn conversation — the savings compound fast.\n\nThis is also why caching improves **speed**, not just cost. Skipping the re-processing step means the model can start generating its answer sooner — providers report cutting time-to-first-word by up to 80%+ on long prompts.\n\n---\n\n## Where this really pays off (real scenarios)\n\n- **Customer support / chat assistants** — same system prompt and tool list on every single user turn, across every single user. Massive repeated prefix.\n- **\"Chat with your PDF\" tools** — you paste in a 20-page document once, then ask multiple questions about it. The document text becomes the cached prefix; only your questions change.\n- **Coding assistants** — a large codebase or set of instructions gets reused across many small follow-up edits.\n- **Agents that call tools repeatedly** — every tool definition gets resent on every step of a multi-step task; caching keeps that from being re-billed every step.\n\nWhere it does **not** help: one-off requests, prompts that are different every time with no shared prefix, or conversations where the system prompt/instructions change on every single call.\n\n---\n\n## Common misconception, worth stating clearly\n\nPeople sometimes assume prompt caching means the model is becoming \"stateful\" — that it's starting to genuinely retain memory of you between sessions. It isn't. This is purely a performance optimization sitting underneath the model, invisible to it. The model's actual behavior, its answer, is (in principle) identical whether the cache was used or not — caching only changes _how fast and how cheaply_ that identical answer gets produced. You still have to manage and resend your own conversation history and context every time; caching doesn't remove that responsibility, it just makes the resending cheaper when the resent part is unchanged.\n\nIt's also worth knowing that this cache is kept private and isolated — your organization's cached data isn't shared with, or accessible to, other customers, even if by coincidence they happen to use a very similar prompt.\n\n---\n\n## For the applied AI engineers reading this: a bit more depth\n\nIf you're actually building with these APIs, here's what matters practically:\n\n**It's prefix-order sensitive.** Put the stuff that never changes _first_ (system prompt, tool definitions, static reference documents), and put the stuff that changes every turn (the newest user message) _last_. If you interleave static and dynamic content, you break the matching prefix and lose the cache benefit for everything after the change.\n\n**There's a minimum size.** Most providers require a cached block to be at least ~1,000 tokens before it's worth caching — trying to cache a 50-token system prompt won't do much.\n\n**Explicit vs automatic caching varies by provider.** Some providers cache automatically behind the scenes with no configuration needed. Others require you to explicitly mark where the cacheable portion of your prompt ends, using a parameter in your API call. Check your specific provider's docs — this changes how much control (and how much savings) you actually get.\n\n**Cache lifetime is short by default.** If your app has bursty, spaced-out traffic (e.g., a user comes back every 20 minutes), you may fall outside the default cache window and pay full price again. Some providers let you pay a bit more for extended retention if this matters for your use case.\n\n**It composes with your own conversation-loop design.** If you're manually managing multi-turn context (appending history, tool results, etc. and re-sending it), structuring that history to stay as a stable, append-only prefix (rather than editing/reordering past messages) is what keeps cache hits high turn after turn.\n\n---\n\n## The one-sentence summary\n\nPrompt caching is the AI provider saying: \"If you send me the same beginning again, I won't waste time and money re-understanding it from scratch — I'll just remember the work I already did, for a little while, and pick up right where that left off.\"\n\nIt doesn't make the model smarter, and it doesn't give it real memory — it just makes repeated, structured conversations dramatically cheaper and faster, which is exactly the shape of workload most real AI products actually have.\n","date_published":"2026-07-24T09:00:00Z","date_modified":"2026-07-24T09:00:00Z","tags":["AI Systems","Prompt Caching","LLMs","KV Cache","Attention","Inference","API Costs"],"image":"https://ommishra.tech/blog/prompt-caching-explained/cover.png","authors":[{"name":"Om Jee Mishra","url":"https://ommishra.tech"}]}]}