Skip to content

All lessons

Area 05 // AI System DesignLesson 79 / 14014 min read

Streaming, time to first token and throughput

Streaming sends the answer to the user token by token as it's generated, so they start reading after the time to first token instead of waiting for the whole reply, and every serving system trades per-user speed (latency) against how many users it can serve at once (throughput).

Think of it like this

Analogy

Ordering at a restaurant: if the kitchen brings each course as soon as it's ready, you start eating within minutes (streaming, fast time to first token). If they bring all courses together at the end, you sit hungry for an hour, even though the cooking took the same time.

And the kitchen has a choice: cook for one table at a time (each table served fast, few tables per night) or many tables together (more tables served per night, each waits a little longer). That's latency vs throughput.

The problem it solves

A long answer can take 10–30 seconds to generate. If the user sees nothing until it's complete, it feels broken; many give up or click again (doubling your cost). At the same time, operators want to serve as many users as possible per GPU or per pound, which tends to make each individual response slower. You need to design for both perceived speed and capacity.

How it works: streaming responses

  1. Ask the API to stream (stream=True or a streaming helper). Instead of one response at the end, you receive a sequence of events: text deltas, tool-call pieces, and a final message with usage.
  2. Forward tokens to the client as they arrive, usually with server-sent events from your backend to the browser (SSE and WebSockets).
  3. Render incrementally in the UI (and show a "thinking" indicator until the first token).
  4. Handle the edges: users disconnecting (stop generation to save cost), errors mid-stream, partial tool calls, and post-processing that needs the full text (validation, citations) at the end.
  5. Streaming doesn't speed up generation, but it changes what users experience, and allows early cancellation.

How it works: time to first token

  1. TTFT = time from sending the request to receiving the first output token.
  2. It's made of: network, queueing at the provider or server, and prefill (processing the whole prompt) (prefill vs decode).
  3. Long prompts raise TTFT (more prefill); prompt caching and prefix caching cut it.
  4. Reasoning models may "think" before the first visible token; stream progress or thinking summaries to fill the silence.
  5. Track TTFT p50/p95 separately from total latency (latency metrics).

How it works: throughput vs latency

  1. Latency is how long one request takes (TTFT + generation time); users feel it.
  2. Throughput is how much work the whole system does per second (tokens/s or requests/s); your bill and capacity depend on it.
  3. Batching links them: a GPU can process many sequences per step almost as fast as one, so bigger batches raise throughput a lot, while each user's tokens per second drops somewhat.
  4. Pick per workload: chat and voice want low latency (smaller batches, faster models); offline jobs want maximum throughput (big batches, batch APIs).
  5. Serving engines like vLLM use continuous batching to get high throughput while keeping latency reasonable.
Where the time goes in one streamed response.

See it in code

Needs
Nothing: plain Python 3. Timings are computed from a simple model so the numbers are the same everywhere.

TTFT vs total time for one streamed answer, and how batch size trades per-user speed for total throughput.

streaming_and_throughput.py
# A streamed answer: a pause for prefill, then tokens one by one.
answer = "You get 25 days of annual leave plus public holidays .".split()
prefill_s, per_token_s = 0.4, 0.02                   # example timings
 
ttft = prefill_s + per_token_s                        # the first token arrives here
total = prefill_s + len(answer) * per_token_s         # the last token arrives here
print(f"TTFT {ttft:.2f}s, total {total:.2f}s for {len(answer)} tokens")
print("without streaming the user stares at nothing for the whole total time")
 
# Throughput vs latency: bigger batches serve more users per second, but each one waits longer.
def step_time_ms(batch):                              # a decode step gets slower as the batch grows
    return 20 + 0.6 * batch
for batch in (1, 8, 32, 128):
    per_user_tps = 1000 / step_time_ms(batch)        # tokens/s each user sees
    total_tps = per_user_tps * batch                   # tokens/s the GPU produces
    print(f"batch {batch:3}: {per_user_tps:5.1f} tok/s per user, {total_tps:6.0f} tok/s total")
Output
TTFT 0.42s, total 0.62s for 11 tokens
without streaming the user stares at nothing for the whole total time
batch   1:  48.5 tok/s per user,     49 tok/s total
batch   8:  40.3 tok/s per user,    323 tok/s total
batch  32:  25.5 tok/s per user,    816 tok/s total
batch 128:  10.3 tok/s per user,   1322 tok/s total

Going from batch 1 to 128 multiplies total throughput by about 27× while each user's speed drops by about 5×. Streaming with a real SDK looks like this (Anthropic):

stream_anthropic.py
import anthropic
 
client = anthropic.Anthropic()
with client.messages.stream(model="claude-haiku-5-5", max_tokens=500,
                            messages=[{"role": "user", "content": "Explain TTFT in one paragraph."}]) as stream:
    for text in stream.text_stream:          # text arrives in small pieces
        print(text, end="", flush=True)     # forward each piece to the client (e.g. via SSE)
    final = stream.get_final_message()       # full message + usage at the end

A real example

Example

A legal assistant that felt twice as fast

A legal assistant produced long answers (about 800 tokens) and users complained it was slow: 18 seconds before anything appeared. The team turned on streaming, shortened the system prompt and cached it, and moved a pre-processing step off the critical path. TTFT dropped to 1.1 seconds; total time barely changed, but satisfaction scores for "speed" doubled and abandoned requests fell sharply. They also stopped generation when users navigated away, saving about 7% of output tokens.

The easy way

Start here

Turn on streaming in your API calls and in your UI. Measure two numbers: time to first token and total time. Keep prompts short and stable (for caching) to lower TTFT.

When you grow: the production setup

Teams set SLOs per feature (TTFT p95 for chat, throughput for batch), choose models and batch limits accordingly, use serving engines with continuous batching and prefix caching, stream through SSE with backpressure and cancellation, validate or post-process at the end of streams, and monitor queue time separately from generation time.

Trade-offs

What you gain

  • Streaming makes long answers feel fast and allows early cancellation.
  • Batching gives much higher throughput per GPU.
  • Separate TTFT and throughput metrics show where to optimise.

What it costs you

  • Streaming complicates validation (you only know the full answer at the end) and error handling.
  • Large batches slow down each individual request.
  • Proxies and load balancers must support long-lived streaming connections.

When not to use it

  • The output must be validated before anyone sees it (e.g. strict JSON for a machine, or high-risk content): generate fully, check, then return.

Common mistakes

  • Measuring only total latency and ignoring TTFT.
  • Streaming text that later fails validation, with no way to retract it.
  • Not cancelling generation when the user leaves.
  • Proxy timeouts or buffering that break streams.
  • Maximising batch size for a latency-sensitive chat product.

Interview questions

Try answering out loud first, then open the model answer.

How does streaming affect latency, and how would you implement it?

Model answer

Streaming doesn't reduce total generation time but cuts perceived latency to the time to first token, since users read as tokens arrive. Implement by calling the model with streaming enabled, forwarding text deltas to the client over SSE (or WebSockets), rendering incrementally, handling disconnects by cancelling generation, managing errors mid-stream, and doing final validation or citation checks when the stream completes.

Explain the throughput vs latency trade-off in LLM serving.

Model answer

GPUs process a batch of sequences per decode step at nearly the cost of one, so batching many requests greatly increases total tokens per second (throughput) and lowers cost per token. But larger batches make each step slower and add queueing, raising per-request latency. Real-time products cap batch sizes and use faster models to meet latency SLOs; offline workloads maximise batching.

What contributes to time to first token and how do you reduce it?

Model answer

Network round-trip, queueing at the server, and prefill of the input prompt (proportional to its length), plus any pre-processing in your app and reasoning before visible output. Reduce it with shorter prompts, prompt or prefix caching, smaller or faster models, streaming reasoning summaries, moving work off the critical path, and capacity or priority tiers to cut queueing.

Quiz

Pick an answer to see whether it’s right and why.

  1. 01What does streaming improve?
  2. 02What mostly determines time to first token?
  3. 03A server doubles its batch size. What usually happens?
  4. 04Which protocol do most LLM APIs use to stream tokens?
  5. 05For a voice assistant, which metric matters most?

0 of 5 answered

Key terms

Streaming (responses)
Sending the model's reply token by token as it is generated, so the user starts reading within a second instead of waiting for the whole answer.
Time to first token (TTFT)
How long it takes from sending a request until the first token of the reply arrives. Key for perceived speed in chat.
Throughput
How much work a system finishes per second, e.g. requests per second or tokens per second across all users. Batching raises throughput, sometimes at the cost of per-request latency.
Latency
How long one request takes. For LLMs it splits into time to first token and the time per output token after that.
Server-sent events (SSE)
A simple HTTP standard for a server to push a stream of text events to the browser over one long-lived response. Most LLM APIs stream tokens this way.
Batching
Processing several requests together in one pass through the model, so the weights are read once for many users. It's the main way to raise GPU throughput.

Before and after

Progress is saved in this browser only.