Coding with AI without overspending

Almost everything written about coding with artificial intelligence is about which model is better. In practice, when the bill arrives, the model you chose explains a minor part of the total. What explains it is how many times you sent the same information again.
This article is about that: what exactly you pay for, and which practices lower the spend without making the result worse. The examples use two Anthropic models — Claude Sonnet 5 and Claude Opus 5 — because they cover both ends of the problem, but the reasoning applies to any provider.
What you pay for
A language model charges by tokens: chunks of text of about four characters. “coding” is one token; “internationalisation” is several. The ones that go in and the ones that come out are charged separately:
| Model | Input per million | Output per million | Context |
|---|---|---|---|
| Claude Sonnet 5 | 2 USD | 10 USD | 1M tokens |
| Claude Opus 5 | 5 USD | 25 USD | 1M tokens |
Opus costs exactly 2.5 times what Sonnet costs, on input and on output. And output costs five times more than input on both.
Now the fact that changes everything: a conversation has no memory. Every time the model answers, the program sends it the whole previous conversation again. On turn twenty you are not paying for twenty messages: you are paying for the sum of every prefix, which grows quadratically. One long coding session can cost more than twenty short sessions with the same content.
That is why the first useful question is not which model to use, but what am I sending and how many times.
1. Prompt caching: the only improvement with no downside
This is the first thing to do, before anything else, because it involves no difficult decision.
If you repeatedly send the same block of text — the system instructions, the project documentation, the file you are working on — the provider can store it already processed and reuse it. In Anthropic’s API that feature is called prompt caching, and the numbers are these:
- Writing to the cache the first time: 1.25 times the normal price.
- Reading from the cache afterwards: one tenth of the normal price.
That is, from the second use onwards you pay 10% for that portion. With a 50,000 token context repeated thirty times, the difference between using the cache and not using it is roughly ten to one.
The detail that makes it not work. The cache matches by prefix: it is reused as long as the beginning of the request is identical byte for byte. A single different character at the start invalidates everything that follows. The most common invalidators are:
- A timestamp in the system instructions.
- A random identifier per request.
- A serialised JSON object without sorted keys.
- A tool list that changes order between calls.
The practical rule is to put what is stable first and what varies at the end. And
you verify it without guessing: the API response carries a
cache_read_input_tokens field. If it is zero when there should be a match,
there is an invalidator in the prefix.
const r = await client.messages.create({
model: "claude-sonnet-5",
max_tokens: 16000,
cache_control: { type: "ephemeral" },
system: projectContext, // stable: gets cached
messages: [{ role: "user", content: currentQuestion }], // variable: at the end
});
console.log(r.usage.cache_read_input_tokens); // if 0, something broke the prefix
2. Before switching models, try lowering the effort
The intuitive reaction to a high bill is to move everything to the cheaper model. That is usually a worse deal than it looks, for two reasons.
The first: the cache is per model. If you split the work between Sonnet and Opus, each keeps its own cache. The same context that was reused thirty times gets reused fifteen times on each side, and you pay the initial write twice. You can end up spending more than before.
The second: there is an intermediate lever. Current models accept an
effort level, which regulates how much the model reasons before answering:
from low to max. It is a different decision from choosing a model, and in
practice the more capable model at low effort tends to perform as well or better
than a smaller one at high effort — at a comparable cost, and without splitting
the cache in two.
The right order to try things is this:
- Turn on the cache. No downside.
- Lower the effort on the paths where quality holds.
- Only then, evaluate a cheaper model.
For coding, high is usually the balance point and xhigh is worth it when the
task is long and has many steps. low is fine for mechanical work: renaming,
reformatting, translating a configuration file.
3. Measure cost per finished task, not per request
This is the most expensive reasoning mistake, and the least obvious.
If you compare two models by what one call costs, the cheaper one always wins. But a model that needs four attempts to produce code that compiles is not cheaper than one that solves it in a single attempt — it is 1.6 times more expensive, and on top of that it consumed your time reviewing the three failed attempts.
The right unit is the cost of completing the task: every turn, every retry, all the context resent until the result is usable. Measured that way, the ranking changes often.
And there is a cost that appears on no invoice: the time of whoever reviews it. If the code arrives with subtle bugs, somebody goes looking for them. That while is worth considerably more than the difference between 2 and 5 dollars per million tokens.
4. Anything not interactive goes in a batch
If you are not waiting for the result on screen — documenting a whole module, generating tests for fifty files, classifying tickets — the Batch API processes requests asynchronously at 50% of the price.
It takes longer and is no use for conversational work. For everything else, it is a halving with no consequences.
5. Practices that also improve the result
These are not saving techniques: they are better ways of working that, as a side effect, spend less. And they make the most difference day to day.
Start new conversations. When you finish a task and start another, do not continue in the same thread. Everything earlier keeps being resent on every turn, and it also competes for the model’s attention with what matters now.
Give the error, not a description of the error. Pasting the compiler message or the failing test output is shorter than explaining what is happening, and far more precise. An error message is fifty tokens worth more than five hundred tokens of description.
Do not paste the whole repository. It is tempting to give all the context just in case. But a model with twenty irrelevant files performs worse than one with the three that matter: the noise competes with the signal, and on top of that you pay for it on every turn of the conversation.
Ask for the diff, not the file. If you ask “rewrite this file”, the output is the complete file — and output costs five times more than input. Asking only for the lines that change cuts that part of the bill and also makes reviewing easier.
Write the test first. Giving it a failing test turns an ambiguous instruction into a verifiable criterion. It reduces the number of attempts, which is where the money goes, and leaves you something useful even if the model gets it wrong.
When it is simply not worth it
A language model is not the right tool for everything, and forcing it costs money and time:
- Renaming, reformatting, mechanical replacements. Your editor’s refactoring does it exactly, instantly and for free.
- Anything a type can guarantee. If the compiler can prove it, a model does not need to check it.
- Deciding the architecture of a system that does not exist yet. Without the context of your organisation, your deadlines and your team, the answer will be a generic description of good practices.
In short
The spend almost never comes from having picked the expensive model. It comes from resending the same context without caching, from dragging endless conversations along, and from counting calls instead of counting finished tasks.
If I had to keep two things: turn on prompt caching and measure what it costs to finish a task, not what a request costs. Both are free to implement and they explain most of the difference.
Comments
Sign in to comment and to like this article.
No comments yet. Be the first to write one.