The most common way teams discover the price of an AI feature is the invoice at the end of the month. A feature ships, traffic grows, and a number that looked negligible in testing turns into a bill that sinks the quarter. Latency tells the same story in miniature: a request that felt instant at low load suddenly times out under real traffic, because nobody multiplied one request by a thousand.
Both disasters share one cause: the cost and the latency were never estimated before the model was wired in. This lesson fixes that. You will write a few small functions that turn a prompt and an expected answer into a dollar figure and a millisecond figure, using nothing but arithmetic, before a single model call exists. The model's price changes every quarter; the budgeting method does not.
flowchart TD A["input chars"] --> IT["input tokens = ceil(chars / 4)"] B["output chars"] --> OT["output tokens = ceil(chars / 4)"] IT --> C["cost = inTok * inPrice + outTok * outPrice"] OT --> C C --> D["dollars"] style C fill:#8b5cf6,color:#fff style D fill:#166534,color:#fff
Two ingredients, easily confused
A language-model bill has two ingredients, and mixing them up is the first mistake. Input tokens are what you send in: the prompt, the retrieved documents, the conversation history. Output tokens are what the model generates back. Providers price them separately, and output almost always costs more, because producing text is harder work for the model than reading it.
To turn characters into tokens you need a token estimate. The durable rule of thumb is that one token is roughly four characters of English text - close enough for a budget, never precise enough for a final invoice. Real tokenizers split on sub-words and punctuation, so the exact count differs; but for planning, four characters per token is the estimate the whole field leans on.
function estimateTokens(text) {
return Math.ceil(text.length / 4);
}
// Rough token counts for budgeting, not exact billing.
console.log(estimateTokens("The quick brown fox")); // 19 chars -> 5 tokens
console.log(estimateTokens("a")); // 1 char -> 1 token
Cost is now arithmetic
Estimating cost is arithmetic from here. Multiply the input tokens by the input price and the output tokens by the output price, then add the two. The shape of the function matters far more than any specific number: prices are arguments you pass in, not facts hard-coded into your code, so when a provider changes them you update one input rather than editing every call site.
Notice where the money hides. A long prompt is paid once, but a long response is paid on a more expensive rate and is the part your users actually wait for. Doubling the prompt length is cheap; doubling the output length is expensive in both dollars and seconds. That asymmetry is the single most useful thing to internalise about model budgets.
function estimateTokens(text) {
return Math.ceil(text.length / 4);
}
// rates are dollars per 1000 tokens - you pass YOUR provider's current price.
function estimateCost(inputChars, outputChars, rates) {
const inTok = estimateTokens(inputChars);
const outTok = estimateTokens(outputChars);
return inTok / 1000 * rates.inputPer1k
+ outTok / 1000 * rates.outputPer1k;
}
const rates = { inputPer1k: 1, outputPer1k: 2 }; // example rates only
console.log(estimateCost(4000, 4000, rates)); // 1.0 + 2.0 = 3.0
From one request to a monthly bill
A per-request estimate only matters once you scale it up. Take the cost of a single call, multiply by the calls you expect each day, then by thirty: that is the monthly number that lands on an invoice. A feature that costs a tenth of a cent per call looks free in testing, but at a million calls a day it is three thousand dollars a month.
The same arithmetic exposes the levers you can pull. Cut the output length in half and the monthly bill roughly halves with it. Cache the answer to a common prompt and you pay once instead of a thousand times. Downsize to a cheaper model and the per-token price drops. None of those levers is visible until the per-request estimate is in hand.
flowchart LR P["Read the prompt: one pass"] --> G["Generate output: one token at a time"] G --> L["Latency grows with output length"] style G fill:#b45309,color:#fff style L fill:#b91c1c,color:#fff
Latency follows the same logic, in time
Latency follows the same logic but measured in time. Reading a long prompt is largely a single parallel pass; generating the output is sequential, one token at a time. So the part of the request your user actually feels - the wait for the answer to stream back - scales with the output length multiplied by how long the model takes per output token. A model that emits thirty tokens a second takes a full second to produce a thirty-token reply, prompt aside.
For both cost and latency, plan against traffic, not a single call. Multiply one request's cost by your real request volume, and reason in percentiles - the median request and the slow ninety-fifth - rather than the average, because a few huge requests dominate the average and quietly hide the worst cases.
function estimateTokens(text) {
return Math.ceil(text.length / 4);
}
// latency = prompt read (one pass) + output generated one token at a time
function estimateLatencyMs(inputChars, outputChars, msPerInputToken, msPerOutputToken) {
const inTok = estimateTokens(inputChars);
const outTok = estimateTokens(outputChars);
return inTok * msPerInputToken + outTok * msPerOutputToken;
}
// example rates only: reading is cheap, generation dominates
console.log(estimateLatencyMs(4000, 4000, 1, 30)); // 1000*1 + 1000*30 = 31000 ms
flowchart TD S["Estimate tokens per request"] --> C["Multiply by your real per-token price"] C --> T["Apply real traffic: p50 and p95, not the average"] T --> Q["Is the monthly bill acceptable?"] Q -->|"yes"| SHIP["Ship it"] Q -->|"no"| CUT["Trim output, cache, or downsize the model"] style SHIP fill:#1e7f3d,color:#fff style CUT fill:#b45309,color:#fff
What does this print? Tokens are estimated as the character count divided by 4, rounded up.
function estimateTokens(text) {
return Math.ceil(text.length / 4);
}
console.log(estimateTokens("abcdefgh"));
"abcdefgh" is 8 characters long.
8 divided by 4 is 2, already a whole number.
Write estimateCost(inputChars, outputChars, rates) that returns the dollar cost: input tokens times rates.inputPer1k plus output tokens times rates.outputPer1k, each divided by 1000. Tokens are Math.ceil(chars / 4). Pass rates in - never hard-code a price.
function estimateCost(inputChars, outputChars, rates) {
// tokens = ceil(chars / 4); cost = inTok/1000*inputPer1k + outTok/1000*outputPer1k
}
Compute input and output tokens separately with Math.ceil(chars / 4).
Each term is tokens / 1000 times its per-1k price; add the two terms.
function estimateCost(inputChars, outputChars, rates) {
const inTok = Math.ceil(inputChars / 4);
const outTok = Math.ceil(outputChars / 4);
return inTok / 1000 * rates.inputPer1k + outTok / 1000 * rates.outputPer1k;
}
This estimateCost has the missing-output bug: it charges only for input tokens and drops the output term entirely, so any request with a real reply is under-priced. Add the output term - output tokens times rates.outputPer1k over 1000 - so both halves of the bill are counted.
function estimateCost(inputChars, outputChars, rates) {
const inTok = Math.ceil(inputChars / 4);
return inTok / 1000 * rates.inputPer1k;
}
The function returns only the input term; it needs a second term for output.
Add outTok / 1000 * rates.outputPer1k to the return value.
function estimateCost(inputChars, outputChars, rates) {
const inTok = Math.ceil(inputChars / 4);
const outTok = Math.ceil(outputChars / 4);
return inTok / 1000 * rates.inputPer1k + outTok / 1000 * rates.outputPer1k;
}
Why does a model provider typically charge MORE per token for output than for input?
Generating text is sequential and autoregressive - each new token depends on the ones before it - so it costs the provider more compute than reading the prompt, which is largely a single forward pass. That is why output carries the higher rate.
Write withinBudget(inputChars, outputChars, rates, maxDollars) that returns true if the estimated cost of the request is at or below maxDollars, and false otherwise. This is the gate you would call before sending a request: estimate first, spend second. Tokens are Math.ceil(chars / 4).
function withinBudget(inputChars, outputChars, rates, maxDollars) {
// estimate the cost, then compare to maxDollars
}
Reuse the cost calculation: inTok and outTok each divided by 1000 and multiplied by their price.
Return whether that cost is less than or equal to maxDollars.
function withinBudget(inputChars, outputChars, rates, maxDollars) {
const inTok = Math.ceil(inputChars / 4);
const outTok = Math.ceil(outputChars / 4);
const cost = inTok / 1000 * rates.inputPer1k + outTok / 1000 * rates.outputPer1k;
return cost <= maxDollars;
}
Recap
- A model bill has two ingredients: input tokens (what you send) and output tokens (what comes back), and output is usually priced higher.
- Estimate tokens from characters with the durable rule ceil(chars / 4) - rough, but good enough to plan against.
- Cost = input tokens times the input price plus output tokens times the output price, each over 1000. Pass prices in as arguments; never hard-code a rate.
- Latency is dominated by output: the prompt is read in one pass, but the answer is generated one token at a time.
- Plan against real traffic at p50 and p95, not the average, so a few huge requests cannot hide the worst cases.
- Always estimate input and output separately; dropping the output term is the classic budgeting bug.
Next you will meet the broader shape of an AI system - the deterministic components that surround a single model call and turn it into something you can ship.