Index Ai Academy
Phase 1 · Building with LLMs · Prompting

Tokens and the context window

How text becomes tokens, and what fits in one request.

About 25 minutes · A free account opens the whole lesson

Create a free account Try part of it here

Already have an account? Sign in to open this lesson

Why it matters

Tokens are the unit behind every limit in an LLM app. They set how much you can send and how long the reply can be. They also set how long a call takes and what it costs. A prompt that works on ten customer reviews can fail on ten thousand. It can also slow down or run up a bill. If you think in tokens, you can size a request before you send it. You won't have to learn the limits from an error message or an invoice.

What you'll be able to do

  • Estimate how many tokens a piece of text will use, and explain why exact counts vary
  • Turn the usage numbers in a response into a cost estimate
  • Check that a request fits the context window with room left for the reply
  • Decide what to send when you have more material than budget

What it covers

  1. Text in, tokens in
  2. Exact counts come from the API
  3. What a request costs
  4. Worked example: what did that request cost?
  5. The context window
  6. Bigger isn't automatically better
  7. Choosing what to send
  8. Worked example: packing reviews into a budget

Text in, tokens in

Picture a box of fridge-magnet poetry. Common words come as whole magnets: "the", "sale", "free". A rare word has no magnet of its own, so you build it from smaller pieces. Models read text in a similar way.

Before a model sees your prompt, a program called a tokenizer cuts the text into pieces. Each piece is a token. The tokenizer can only use pieces from a fixed list, its vocabulary. It swaps each piece for a number, called its token ID. The model only ever works with those numbers, never with the letters.

The vocabulary is learned from huge amounts of text. Pieces that show up often get their own token. That leads to a few habits:

  • Common words are usually one token. The token often includes the space before the word: sale, the, email.
  • Rare words, brand names and long words split into several pieces. Trailhead might become Trail + head.
  • Numbers, web links, code and punctuation tend to use more tokens than their length suggests. A tracking link is the extreme case: the https prefix, a product code such as RJ-2291 and tags like utm_source and utm_medium are all rare pieces, so each breaks into several tokens.
  • Languages other than English, and emoji, often need more tokens per word.

For English prose there's a handy rule of thumb:

1 token ≈ 4 characters ≈ ¾ of a word

In plain words: count the letters, spaces and punctuation, then divide by 4. Or take the number of words and add a third.

Here's a quick example. A 100-character sentence is about 100 ÷ 4 = 25 tokens. A 750-word article is about 1,000 tokens, because 750 ÷ ¾ = 1,000. These are rough guides, not exact counts.

Try the splitter below. Paste in a tracking link or a product code and watch the count jump.

Try it now

This splitter copies a real tokenizer's habits, but it isn't one. Delete the link and watch the characters per token go up. Plain English packs more characters into each token.

Quick check
About how many tokens is a 600-word blog post in plain English?
  1. Exactly 600
  2. About 2,400
  3. About 800
  4. About 450
Show the answer

About 800

One token is about three-quarters of a word, so you need more tokens than words: 600 ÷ ¾ = 800. Exactly 600 would mean one token per word, which is too few. 2,400 multiplies the words by 4, but the four-characters rule is about characters, not words.

Exact counts come from the API

The splitter and the four-characters rule are estimates. Each family of models has its own tokenizer. So the same text can come to a different number of tokens on different models, even from the same company. There are two ways to get exact numbers:

  • After a request: the response has a usage section. It reports input_tokens (what you sent) and output_tokens (what the model wrote).
  • Before a request: Anthropic has a token-counting endpoint, a web address the API answers at: POST /v1/messages/count_tokens. You send it the same body as a normal request. It replies with {"input_tokens": ...} and doesn't write a reply. The academy's proxy is the server that passes your requests on to Anthropic. It only forwards /v1/messages, so in this course you'll use estimates.

For planning, an estimate is enough. The one below rounds up, so a short string never counts as zero:

import math

def estimate_tokens(text):
    return math.ceil(len(text) / 4)

samples = [
    "Free shipping this weekend",
    "Our organic cotton tees are made to last through years of weekend wear.",
    "https://example.com/sale?utm_source=newsletter&utm_campaign=spring_2026",
]
for s in samples:
    print(estimate_tokens(s), "tokens (estimated):", s)
It runs right here in your browser, with nothing to install.

len(text) counts the characters. math.ceil rounds up to the next whole number. So the 26-character first line comes to 26 ÷ 4 = 6.5, which rounds up to 7 tokens.

The sentence and the link both come out at 18 tokens. That's because each is 71 characters long. A real tokenizer would almost certainly use more tokens on the link. It's full of punctuation and made-up words like utm_campaign. Estimates from character counts are rough, so leave a margin.

Quick check
Using the same rule, what does this print?
import math
print(math.ceil(len("Spring sale") / 4))
  1. 11
  2. 2.75
  3. 2
  4. 3
Show the answer

3

"Spring sale" has 11 characters, counting the space. 11 ÷ 4 = 2.75, and math.ceil rounds up to 3. It never rounds down, so 2 is wrong, and it always gives a whole number.

What a request costs

You pay per token, and the price is quoted per million tokens. Input tokens (what you send) and output tokens (what the model writes) have separate prices. To price one call:

cost = tokens ÷ 1,000,000 × price per million

Do this once for input and once for output, then add the two.

In plain words: work out what share of a million tokens you used. Then take that share of the price.

Here's an example with small numbers. Say input costs $4 per million tokens and output costs $20. A call sends 3,000 tokens and gets 800 back.

  • Input: 3,000 ÷ 1,000,000 = 0.003, and 0.003 × $4 = $0.012.
  • Output: 800 ÷ 1,000,000 = 0.0008, and 0.0008 × $20 = $0.016.
  • Total: $0.012 + $0.016 = $0.028, a little under three cents.

The output was far shorter, yet it cost more, because each output token costs five times as much. The worked example below does the same sum in Python, using the real usage numbers from a call.

Worked example

Worked example: what did that request cost?

Every response reports how many tokens it used. Multiply those counts by the prices to see what a call cost. Prices are quoted per million tokens. The ones below are Claude Opus 5.5's at the time of writing, so check Anthropic's pricing page before you rely on them.

import requests

INPUT_PRICE = 4.00    # dollars per million input tokens
OUTPUT_PRICE = 20.00  # dollars per million output tokens

response = requests.post(
    "https://api.anthropic.com/v1/messages",
    headers={"x-api-key": "added-by-the-academy-server", "anthropic-version": "2026-10-21"},
    json={
        "model": "claude-opus-5-5",
        "max_tokens": 1000,
        "messages": [{"role": "user", "content": "Suggest three names for a newsletter about trail running."}],
    },
    timeout=60,
)
response.raise_for_status()
usage = response.json()["usage"]

input_cost = usage["input_tokens"] / 1_000_000 * INPUT_PRICE
output_cost = usage["output_tokens"] / 1_000_000 * OUTPUT_PRICE
cost = input_cost + output_cost

print("Input tokens:", usage["input_tokens"])
print("Output tokens:", usage["output_tokens"])
print(f"This call: ${cost:.6f}")
print(f"50,000 calls like it: ${cost * 50_000:.2f}")
In the lesson you can change this and run it.

One call costs a fraction of a cent, so it's easy to ignore. Now picture a nightly job that runs over 50,000 reviews. The total becomes a line on a budget that someone will ask about.

Here, output tokens cost five times as much as input tokens. Some models work through a problem in writing before they answer. That working-out is called thinking, and it counts as output too. So long replies drive most of the bill. The underscores in 1_000_000 only make the number easier to read. Python ignores them.

The context window

Think of a suitcase with a weight limit. Everything you pack counts, and you need to leave room for what you'll bring home. A model's context window works the same way. It's the most tokens the model can handle in a single request. Everything counts against it:

  • the system prompt, the standing instructions you give the model
  • every message in messages, including earlier turns of a conversation
  • any documents, data or tool descriptions you include
  • the reply the model writes, thinking included

Claude Opus 5.5 has a context window of 1 million tokens. One reply can be up to 128,000 tokens long. Some older models offer 200,000. (In the academy, the proxy caps max_tokens at 1,024 to keep costs down.)

The reply has to fit too. So budget each request like this:

input tokens + max_tokens ≤ context window

In plain words: what you send, plus the room you save for the reply, must not be more than the window. The sign ≤ means "less than or equal to".

Here's an example with small numbers. Say a window holds 1,000 tokens. You send 700 tokens and set max_tokens to 400. Then 700 + 400 = 1,100, which is 100 more than the window, so it won't fit. Lower max_tokens to 300 and the total is exactly 1,000, which fits. In Python, the check is one line: input_tokens + max_tokens <= window.

If what you send doesn't fit, the API turns the request down. It replies with a 400 error, the code for "something is wrong with this request". You'll need to send less.

Quick check
A request has a 2,000-token system prompt and 995,000 tokens of documents. It sets max_tokens to 8,000, on a model with a 1-million-token window. What's the problem?
  1. There's no problem, because the reply will be limited to 2,000 tokens automatically
  2. The API will drop the system prompt to make room, so the rules won't apply
  3. Input plus max_tokens comes to 1,005,000, more than the window, so trim the documents or lower max_tokens
  4. There's no problem, because only the documents count toward the window
Show the answer

Input plus max_tokens comes to 1,005,000, more than the window, so trim the documents or lower max_tokens

The input is 2,000 + 995,000 = 997,000 tokens. Add the 8,000 saved for the reply and you get 1,005,000. To get under the window, cut documents the task doesn't need, or lower max_tokens.

Bigger isn't automatically better

A million tokens is several long novels. That doesn't mean you should fill it:

  • Cost: you pay for every input token, on every call.
  • Speed: more input takes longer to read before the first word of the reply appears.
  • Focus: models handle long inputs well. Still, a key detail buried among thousands of others is easier to miss than the same detail in a short prompt. Send what the task needs.

Conversations make this worse. The API doesn't remember earlier turns. So each new request sends the whole conversation again, every message and every reply. The code below follows five short turns. Each of your messages is 150 tokens, and each reply is 400.

history_tokens = 0
for turn in range(1, 6):
    history_tokens += 150   # your new message
    print(f"Turn {turn}: sending {history_tokens} input tokens")
    history_tokens += 400   # the reply joins the history for next time

The input grows by 550 tokens each turn: 150, 700, 1,250, 1,800, then 2,350. By the fifth short turn, one request sends 2,350 input tokens. You pay for every one of them again. The Conversations and memory lesson covers ways to keep that in check.

Quick check
In the conversation code, suppose each reply were 200 tokens instead of 400, while your messages stay at 150. How many input tokens would turn 3 send?
  1. 1,250
  2. 600
  3. 450
  4. 850
Show the answer

850

Turn 3 sends your first two messages, the two replies and your new message: 150 + 200 + 150 + 200 + 150 = 850. The answer 450 forgets the replies. 1,250 is the original number, with 400-token replies.

Choosing what to send

Sometimes you have more material than room, such as 5,000 reviews or a year of support tickets. Think of packing that suitcase again. Decide what goes in, in this order:

  1. Save room for the reply: subtract max_tokens.
  2. Subtract the fixed parts: the system prompt and instructions.
  3. Fill what's left with the most useful material first. That might be the newest reviews or the most relevant documents. Add each item only if it still fits.

The retrieval lessons later in Phase 1 show how to find the most relevant pieces automatically. The packing itself takes a few lines of code.

Worked example

Worked example: packing reviews into a budget

You have room for 30 tokens of reviews, and the list is in newest-first order. Add each review that still fits, and skip any that would overflow.

import math

def estimate_tokens(text):
    return math.ceil(len(text) / 4)

reviews = [
    "Arrived in two days and fits perfectly.",
    "The zipper broke after one hike. I want a refund and an apology from someone in charge.",
    "Great color.",
    "Runs small, order a size up.",
]

budget = 30
chosen = []
used = 0
for review in reviews:
    cost = estimate_tokens(review)
    if used + cost <= budget:
        chosen.append(review)
        used += cost
    else:
        print(f"Skipped ({cost} tokens): {review}")

print(f"Using {used} of {budget} tokens")
for review in chosen:
    print("-", review)
In the lesson you can change this and run it.

The four reviews cost about 10, 22, 3 and 7 tokens. The first one fits, so 10 tokens are used. The complaint needs 22 more, which would push the total to 32. That's over 30, so it's skipped. The next two still fit, and the total ends at 20 of 30.

Whether that's right depends on the job. Say you want a summary of problems. Then the complaint is the review you'd least want to lose. You would sort complaints to the front before packing. The loop keeps you inside the budget. Deciding what matters most, and ordering the list that way, is still your call.

Quick check
Run the packing example again with budget = 45 instead of 30. Which reviews get in?
  1. Only the complaint, because it's the longest
  2. Only the first two, using 32 tokens
  3. All four, using 42 tokens
  4. The same three as before, using 20 tokens
Show the answer

All four, using 42 tokens

The costs are 10, 22, 3 and 7. With room for 45, the running total goes 10, 32, 35, 42, and each step stays under 45. So all four fit, using 42 tokens.

Practice what you just read

  • 4 code examples you can edit and run
  • a practice exercise that checks your code
  • a 5-question quiz
  • 5 review cards for spaced repetition

Create a free account

Try a quiz question

Which of these probably uses the most tokens for its number of characters?

  1. The sale starts today
  2. We are so happy to tell you that our whole new winter collection is finally here
  3. Our new collection is here
  4. https://shop.example.com/p/RJ-2291?utm_source=fb&utm_medium=cpc

Answer it at the end of the lesson to see if you're right, and why.

Before this lesson