AI Engineering Interview Questions — Streaming in Production AI

Reviewed by Mark Dickie · Last updated

Streaming in AI production is the practice of sending model output to the client incrementally as tokens are generated, rather than waiting for the full response to complete. For interview purposes, you should understand the transport mechanisms (Server-Sent Events, WebSocket, chunked HTTP), how backpressure flows from a slow client back to the inference server, and the trade-offs between first-token latency and total response time. Interviewers also expect you to reason about error handling mid-stream, reconnection semantics, and how streaming interacts with structured output formats like JSON.

What does an AI engineering interview test on streaming?

Most streaming questions probe whether you can connect the mechanics of incremental delivery to real production concerns: user experience, infrastructure cost, and correctness. You will likely face a design question ("design a streaming chat endpoint"), a debugging scenario ("users see truncated responses"), or a trade-off question comparing streaming versus batch responses.

ConceptWhat to knowCommon interview angle
Server-Sent Events (SSE)Unidirectional, HTTP-based, auto-reconnect built into the browserWhy pick SSE over WebSocket for LLM token streaming?
Token streamingModel yields partial output per step; you forward each chunkHow do you handle partial JSON during streaming?
BackpressureSlow client can't keep up; server must buffer or dropWhat happens when the client reads slower than the model generates?
First-token latencyTime to first chunk vs. total completion timeWhen is streaming worse than batch for perceived latency?
Reconnection & resumeClient disconnects mid-stream and reconnectsHow do you resume a partially streamed response?

How should you prepare for streaming questions?

  1. Build a minimal streaming endpoint using SSE with your framework of choice, and trace a single token from model output through the transport layer to the browser.
  2. Study backpressure handling: understand what your server does when the write buffer fills up and why unbounded buffering is dangerous under load.
  3. Practice explaining the trade-off between streaming and batch delivery in terms a non-technical stakeholder would follow — interviewers often want both depth and clarity.
  4. Review structured-output streaming: know at least one approach for validating partial JSON (e.g., incremental parsers, sentinel-based framing, or deferring validation to stream end).
  5. Read through the reconnection flow for your chosen transport, including what happens to in-flight tokens when the connection drops and how you would implement a resume-from-offset mechanism.

Key facts

  • Tarmac has 28 AI Engineering interview questions on this topic, 25 of them on this page, at difficulty 1–5 of 5.
  • Tarmac tracked 4,175 job postings asking for AI Engineering in August 2026.
  • Roles asking for AI Engineering advertise a median base salary of US$180,000, across 757 job postings as of August 2026.
  • Tarmac last reviewed these AI Engineering interview questions on 31 August 2026.

At a glance

Questions25 shown · 28 in the bank
Difficulty1–5 of 5
FormatsMultiple choice, Flashcard, Short answer, True / false, Code output, Fill in the blank, Multiple answer, Find the bug, Ordering, Design exercise

What you'll review

  1. streaming
  2. ai production
  3. agent loops
  4. latency cost

Practice questions

Try one before you open the answer. Pick an option and press Check; it's marked on the spot.

AI Engineering/ai-production/streaming

In the context of calling an LLM API (e.g., OpenAI's Chat Completions), what does it mean to use a streaming response?#

Options

Show answer

Streaming in an LLM API call means the server sends output tokens incrementally as they are generated, letting the client start displaying or processing text before the full response is complete. This reduces time-to-first-token and improves perceived latency, unlike batching, polling, or compressing the whole response before sending.

Why:

Streaming means the server emits tokens (or chunks) as they are produced rather than waiting for the entire generation to finish. The client receives each chunk incrementally and can display or process it immediately. This improves perceived latency—time-to-first-token—because the user sees output before the full response is ready. The other options describe batching, polling, or compression, none of which is streaming.

AI Engineering/ai-production/streaming

In LLM inference, what is the standard term for delivering tokens to the client incrementally as the model generates them, and which common HTTP-based transport mechanism is frequently used to implement it?#

Show answer

The term is streaming (or token streaming). The most common HTTP-based transport mechanism used to implement it is Server-Sent Events (SSE), where the server keeps a single long-lived HTTP response open and sends chunks (e.g., individual tokens) as they are produced, so the client sees incremental output without waiting for the entire response to finish.

Why:

The standard term for delivering model output incrementally as tokens are generated is 'streaming' (or 'token streaming'). SSE is the most common HTTP-based transport for implementing streaming because it is simple, unidirectional (server-to-client), and works over a standard HTTP connection, making it ideal for sending tokens as they are generated.

AI Engineering/ai-production/streaming

In a production LLM application, what does it mean to 'stream' a model's response back to the client?#

Options

Show answer

Streaming a model response means sending tokens (or small token chunks) to the client incrementally as the model generates them, rather than waiting for the full response to finish. This is commonly implemented with Server-Sent Events or chunked transfer encoding and reduces perceived latency because the user sees text appear in real time.

Why:

Streaming in the context of LLM production applications means the server emits tokens (or small batches of tokens) to the client as the model generates them, rather than waiting for the entire response to complete. This is typically implemented via Server-Sent Events (SSE) or chunked HTTP transfer encoding, and it reduces the perceived latency the user experiences because text begins appearing immediately. Option (a) is the non-streaming approach, (c) describes transport compression, and (d) describes a queue-based async pattern, none of which is what 'streaming' refers to.

AI Engineering/ai-production

In a production LLM application, what does the temperature sampling parameter primarily control?#

Options

Show answer

The temperature parameter controls the randomness of token selection during generation. A temperature of 0 makes the model deterministic, while higher values increase variability and creativity by flattening the probability distribution over tokens. It does not affect token limits, speed, or concurrency.

Why:

Temperature scales the logits before the softmax in LLM decoding. A value of 0 makes the model deterministic (always pick the highest-probability token), while higher values flatten the distribution, increasing randomness and variability in the output. It does not control token limits, generation speed, or concurrency.

AI Engineering/ai-production

What is an LLM hallucination in the context of a production AI application?#

Show answer

A hallucination occurs when the model generates text that sounds plausible and confident but is factually incorrect or entirely fabricated — e.g., inventing citations, misstating facts, or fabricating API behavior. Production systems mitigate this with retrieval grounding (RAG), output validation, and guardrails.

Why:

Hallucination is a foundational concept in AI production engineering. It refers to the model producing fluent but ungrounded or false output. Recognizing it is the first step toward deploying mitigation strategies such as RAG, fact-checking layers, and structured output validation.

AI Engineering/ai-production/streaming

Why do production chat UIs stream tokens, and what does streaming improve versus what it does not?#

Show answer

Streaming sends tokens to the client as they're generated rather than waiting for the full completion, which dramatically lowers perceived latency — the user sees the first token (time-to-first-token) in a fraction of a second instead of staring at a spinner for the whole response. What it does not change is the total time to finish or the total cost: the same number of tokens are generated and billed, so streaming improves perceived responsiveness, not throughput or price.

Why:

Streaming is purely a latency-perception win: tokens are flushed incrementally so the user gets immediate feedback (low time-to-first-token), which matters a lot for long generations. It does not reduce total generation time, total tokens, or cost — those are fixed by the output length. It does add engineering cost: you must handle partial output, parse incrementally, and deal with mid-stream errors and cancellation.

AI Engineering/agents/agent-loops

Streaming an agent's output reduces the total wall-clock time a run takes to reach its final answer.#

Options

Show answer

False. Streaming changes when the user sees the first token, not when the run finishes: the same model calls happen, the same tools run, the same total time elapses. It moves perceived latency rather than real latency — often the best change available, since a user watching text appear waits more patiently than one watching a spinner. Measure it honestly as two numbers, time-to-first-token and time-to-final-answer, or it will look like a fix for a latency problem it has not touched.

Why:

Streaming changes when the user sees the first token, not when the run finishes. The same model calls happen, the same tools run, the same total time elapses — you have moved perceived latency, not real latency, and that distinction matters when you are choosing what to optimise. It is still frequently the best change available, because a user watching text appear waits far more patiently than one watching a spinner. Just measure it honestly: report time-to-first-token and time-to-final-answer as separate numbers, or streaming will appear to fix a latency problem it has not touched, and the actual levers — fewer iterations, concurrent tool calls, a trimmed transcript, a workflow for the common path — go unexamined.

AI Engineering/ai-production/streaming

When an LLM inference endpoint is configured with response streaming (e.g., stream: true in the OpenAI API), what is the primary user-experience benefit compared to receiving the full response in a single HTTP response?#

Options

Show answer

Streaming LLM responses primarily reduces time-to-first-token: the client begins receiving partial output as soon as the model produces its first token, rather than waiting for the entire response to be generated. This improves perceived latency. It does not reduce total token cost, extend the context window, or remove the need for rate limiting.

Why:

Streaming sends tokens incrementally as the model produces them (typically via Server-Sent Events). The total generation time and token count are essentially unchanged, but the client receives the first token much sooner, improving perceived latency. Streaming does not change context-window limits, reduce token costs, or remove the need for rate limiting.

AI Engineering/ai-production/streaming

A common pattern in AI production is to buffer streamed LLM tokens and emit a completed unit (e.g. a sentence) only when a delimiter arrives. What is printed to stdout when the following Python code runs?#

def stream_sentences(chunks):
    buffer = ""
    for chunk in chunks:
        buffer += chunk
        if "." in chunk:
            yield buffer
            buffer = ""
    if buffer:
        yield buffer

result = list(stream_sentences(["Hello", " world.", " How", " are", " you?"]))
print(result)
Show answer
['Hello world.', ' How are you?']
Why:

The generator accumulates each chunk into buffer and yields only when the current chunk contains a period. Tracing the input list ["Hello", " world.", " How", " are", " you?"]: "Hello" → buffer="Hello" (no period, no yield). " world." → buffer="Hello world." — this chunk contains ".", so the accumulated buffer "Hello world." is yielded and buffer resets to "". Next: " How" → buffer=" How"; " are" → buffer=" How are"; " you?" → buffer=" How are you?" (no period, so not yielded during the loop). After the loop ends, buffer is truthy (" How are you?"), so the final yield is " How are you?". The complete list is ['Hello world.', ' How are you?'], and print outputs that representation.

AI Engineering/ai-production

Your production service calls an LLM API and starts receiving repeated HTTP 429 (Too Many Requests) responses under load. Which retry strategy is the established best practice for handling this?#

Options

Show answer

The established best practice is exponential backoff with jitter, while honoring any Retry-After header the API returns. HTTP 429 means the provider is throttling requests, so progressively increasing the delay between retries—jittered to avoid synchronized client retries—relieves pressure without wasting calls. Ignoring the header or retrying immediately just deepens the rate-limit condition.

Why:

HTTP 429 signals rate limiting. The standard production pattern is exponential backoff (doubling wait time between retries) combined with jitter (randomized offsets) to avoid thundering-herd effects among concurrent clients. Respecting the Retry-After header lets the client honor the server's explicit guidance on when to retry. Retrying immediately worsens the load, switching models does not address rate limits, and increasing max_tokens is unrelated to throttling.

AI Engineering/ai-production

In a Retrieval-Augmented Generation (RAG) pipeline, retrieved documents are typically split into smaller, often overlapping pieces of text before being embedded or passed to the LLM. This process is called _____.#

Show answer

In a Retrieval-Augmented Generation (RAG) pipeline, retrieved documents are typically split into smaller, often overlapping pieces of text before being embedded or passed to the LLM. This process is called chunking.

Why:

Chunking is the standard term for breaking long documents into smaller, manageable text segments (chunks) so that each piece fits within embedding model input limits and the LLM's context window. Overlapping chunks help preserve context continuity across boundaries. This is a foundational step in building RAG systems for production.

AI Engineering/agents/agent-loops

What does streaming change about how an agent loop handles tool calls?#

Show answer

Tool-call arguments arrive as fragments, so the JSON is incomplete until the block finishes. The loop must accumulate fragments per tool-call id and parse only once that call is complete — parsing a partial buffer throws, and a buffer that happens to parse can execute a truncated argument. Because events interleave across parallel calls, fragments must be keyed by call id rather than appended to one buffer. The loop's shape is unchanged: you still wait for the full assistant turn before executing. Streaming buys the first visible token, not a shorter run.

Why:

This is where hand-rolled loops break when a team switches on streaming for the perceived-latency win. Two bugs recur: concatenating fragments from concurrent tool calls into a single buffer, which corrupts both, and attempting to parse before the block is complete. Keying by tool-call id fixes the first; waiting for the completion event fixes the second. The last line is the one people most often get wrong in interviews — streaming improves time-to-first-token and leaves time-to-final-answer untouched.

AI Engineering/ai-production/streaming

You are building a production web service that streams LLM completion tokens to a browser client in real time as the model generates them. Which of the following are standard, correct practices for this streaming architecture?#

Options

Pick every one that applies.

Show answer

Use Server-Sent Events with text/event-stream, flush each token chunk immediately rather than buffering, and rely on chunked transfer encoding (HTTP/1.1) or HTTP/2 stream frames for partial delivery. Buffering the entire response defeats streaming, and switching to raw TCP is not standard practice — it removes encryption and authentication for negligible gain.

Why:

SSE with text/event-stream is the dominant transport for token streaming from LLM APIs (e.g., OpenAI's streaming endpoint), making (a) correct. For tokens to arrive in real time, the server must flush each chunk immediately instead of buffering, so (c) is correct. Under the hood this relies on chunked transfer encoding (HTTP/1.1) or HTTP/2 stream frames to deliver partial response bodies, so (d) is correct. Option (b) defeats the entire purpose of streaming — the client would see nothing until the full response is done. Option (e) is wrong because abandoning HTTPS removes encryption and authentication for negligible performance gain; TLS overhead is not a meaningful concern for token-rate streaming.

AI Engineering/ai-production/streaming

The following FastAPI endpoint is meant to stream LLM tokens to the browser via Server-Sent Events (SSE). However, the client's EventSource onmessage handler never fires — no tokens appear until the entire stream completes, if at all. Identify the buggy line.#

from fastapi import FastAPI
from fastapi.responses import StreamingResponse
import asyncio

app = FastAPI()

async def generate_stream():
    tokens = ["Hello", " ", "world", "!"]
    for token in tokens:
        await asyncio.sleep(0.1)
        yield f"data: {token}\n"

@app.get("/chat")
async def chat():
    return StreamingResponse(
        generate_stream(),
        media_type="text/event-stream",
    )
Show answer

The bug is on line 11.

Why:

Line 11 terminates each SSE data field with a single \n. Per the SSE specification, an event is only dispatched by the client after a blank line — i.e., two consecutive newlines \n\n — is received. With just one \n, the EventSource parser keeps buffering incoming data into the same event and never fires onmessage, so the user sees nothing until the connection closes. The fix is yield f"data: {token}\n\n".

AI Engineering/ai-production/streaming

In an LLM streaming pipeline, server-sent events arrive as a list of dicts. The function below processes the event stream and assembles the final text. What is the exact string printed to stdout?#

def stream_response(events):
    tokens = []
    for event in events:
        t = event.get("type")
        if t == "done":
            break
        if t == "filter":
            tokens.append("[FILTERED]")
        if t == "token":
            tokens.append(event["text"])
    return "".join(tokens)

events = [
    {"type": "token", "text": "The "},
    {"type": "ping"},
    {"type": "token", "text": "sky "},
    {"type": "filter"},
    {"type": "token", "text": " is blue"},
    {"type": "done"},
    {"type": "token", "text": "ignored"},
]
print(stream_response(events))
Show answer
The sky [FILTERED] is blue
Why:

The loop iterates through events in order. The first token appends "The ". The ping event matches neither "done" nor "filter" nor "token", so nothing is appended. The second token appends "sky ". The filter event appends "[FILTERED]". The third token appends " is blue". The done event triggers break, so the final token ("ignored") is never processed. Joining all appended strings yields "The sky [FILTERED] is blue".

AI Engineering/ai-production

You deploy a supervised model trained on historical data into production. Over time the live input feature distribution begins to diverge from the training distribution, even though labels are not immediately available. What is the standard term for this phenomenon, and name two statistical tests or metrics commonly used to detect it?#

Show answer

The phenomenon is called data drift (or covariate drift / input drift). Two commonly used detection methods are the Kolmogorov-Smirnov (KS) test and the Population Stability Index (PSI). Other valid examples include the Wasserstein distance, KL divergence, or Jensen-Shannon divergence.

Why:

When the distribution of input features in production shifts away from the training distribution — without the labels necessarily being available yet — this is called data drift (also covariate drift or input drift). Common detection techniques compare the statistical distribution of a baseline (training) sample against a recent live sample: the Kolmogorov-Smirnov (KS) test for continuous features, the Population Stability Index (PSI), and information-theoretic measures such as KL divergence, Jensen-Shannon divergence, or the Wasserstein distance. The answer must name the phenomenon and at least two of these methods.

AI Engineering/ai-production/streaming

Order the request lifecycle of a streamed chat completion, from user input to a rendered answer in the browser.#

Put these in order

Show answer

The streamed completion lifecycle runs in this order:

  1. Client sends the request; server opens a streaming connection to the model API
  2. Model prefills the prompt and emits the first token (time-to-first-token)
  3. Server relays incremental token deltas to the client as they arrive
  4. Client appends each delta, progressively rendering the answer
  5. Stream terminates with a stop/finish event and usage totals
Why:

Streaming improves perceived latency: the client opens a stream, the model prefills and emits the first token (TTFT — the metric users feel most), the server relays token deltas (commonly over SSE), the client appends and renders them incrementally, and the stream closes with a finish event carrying the stop reason and token usage. Note that total generation time is unchanged — streaming just surfaces tokens as they're produced instead of waiting for the whole completion.

AI Engineering/ai-production/latency-cost

Perceived responsiveness in streaming LLM applications is dominated by _____ to first token (TTFT), the gap between sending the request and receiving the first output token; once streaming starts, the ongoing delivery rate is measured in tokens per _____ (TPS).#

Show answer

Perceived responsiveness in streaming LLM applications is dominated by time to first token (TTFT), the gap between sending the request and receiving the first output token; once streaming starts, the ongoing delivery rate is measured in tokens per second (TPS).

Why:

TTFT (time-to-first-token) is the latency a user feels before anything appears on screen — driven by prompt-processing (prefill) time, which scales with input token count. TPS (tokens per second) governs how fast the stream fills in afterward — driven by the decode phase and is roughly constant per model/hardware configuration. Optimising for TTFT often means shorter prompts or prompt caching; optimising TPS means smaller models or speculative decoding.

AI Engineering/ai-production/streaming

In the Server-Sent Events (SSE) wire format defined by the HTML5 specification, the HTTP response must set the Content-Type header to _____. When an OpenAI-compatible API streams chat completions over SSE, each event payload is prefixed with the literal data: . Per the OpenAI Chat Completions streaming protocol, the server signals that no more tokens will be sent by emitting a final data: _____ sentinel, after which the client closes the stream.#

Show answer

In the Server-Sent Events (SSE) wire format defined by the HTML5 specification, the HTTP response must set the Content-Type header to **text/event-stream**. When an OpenAI-compatible API streams chat completions over SSE, each event payload is prefixed with the literal data: . Per the OpenAI Chat Completions streaming protocol, the server signals that no more tokens will be sent by emitting a final data: **[DONE]** sentinel, after which the client closes the stream.

Why:

The first blank is pinned to the SSE specification (HTML5), which mandates Content-Type: text/event-stream — this is the only correct value under that spec. The second blank is explicitly scoped to the OpenAI Chat Completions streaming protocol, which defines the sentinel as the literal string [DONE] sent as a final data: [DONE] event. Other LLM providers may use different sentinels, but the question asks specifically about the OpenAI-compatible protocol, so [DONE] is the single correct answer. The client detects this sentinel to distinguish normal stream completion from a mid-stream connection drop.

AI Engineering/ai-production/streaming

You stream LLM token output from a Node.js backend to a browser using Server-Sent Events (SSE). The backend receives tokens from the model API promptly, and the response already sets Content-Type: text/event-stream and Cache-Control: no-cache. The traffic passes through an Nginx reverse proxy with default settings. The browser receives tokens only in batches roughly every 30 seconds instead of incrementally. What is the cause?#

Options

Show answer

Nginx's default proxy_buffering on accumulates the upstream response before forwarding it to the client, causing the batched delivery. To stream SSE tokens through Nginx incrementally, either set proxy_buffering off for the streaming location or have the backend send the X-Accel-Buffering: no response header, which Nginx honors per-request. The EventSource API itself has no batching timer, and HTTP/2 has no inherent 30-second flush interval.

Why:

Nginx enables proxy_buffering by default. When on, Nginx stores the upstream response in memory/temp-file buffers and releases it to the client in large writes, which is precisely the batching behavior described. The fix is either proxy_buffering off in the Nginx location block or having the backend emit X-Accel-Buffering: no — Nginx honors that per-response header to disable buffering for that request. Option (a) is wrong: the EventSource API dispatches events as soon as the browser's network stack delivers complete SSE frames; it has no batching timer. Option (c) is wrong: chunked transfer encoding does not impose large chunk sizes, and the symptom's ~30-second cadence matches proxy flush intervals, not chunk boundaries. Option (d) is wrong: HTTP/2 supports streaming with arbitrarily small DATA frames and has no built-in batching delay.

AI Engineering/ai-production

The following async function streams a chat completion from the OpenAI API. In production it intermittently raises TypeError: sequence item 0: expected str instance, NoneType found. Which line is the bug?#

import asyncio
from openai import AsyncOpenAI

client = AsyncOpenAI()

async def stream_chat(messages: list[dict], max_retries: int = 3) -> str:
    """Stream a chat completion with retries on failure."""
    for attempt in range(max_retries):
        try:
            stream = await client.chat.completions.create(
                model="gpt-4o",
                messages=messages,
                stream=True,
            )
            chunks = []
            async for chunk in stream:
                chunks.append(chunk.choices[0].delta.content)
            return "".join(chunks)
        except Exception:
            if attempt == max_retries - 1:
                raise
            await asyncio.sleep(2 ** attempt)
Show answer

The bug is on line 17.

Why:

Line 17 appends chunk.choices[0].delta.content directly. In the OpenAI streaming protocol, the first chunk typically carries only the role field with content set to None, and the final chunk carries finish_reason with content also None. When any of these None values lands in the chunks list, the "".join(chunks) call on line 18 raises TypeError. The fix is to guard against None, e.g. chunks.append(chunk.choices[0].delta.content or ""). None of the other lines contain a defect: the retry loop, backoff, and streaming setup are all correct.

AI Engineering/ai-production/streaming

In transformer-based autoregressive LLM serving with continuous batching (e.g., vLLM), the prefill phase (parallel processing of all prompt tokens) and the decode phase (autoregressive generation of one token at a time) can be batched together in the same forward pass without any modification to the attention computation, because both phases use the same causal self-attention mechanism.#

Options

Show answer

False. Prefill computes attention as a parallel matrix–matrix operation across all prompt tokens, whereas decode computes matrix–vector attention for a single new token against the KV cache. Batching both phases in one forward pass (as vLLM's chunked prefill does) requires specialized attention kernels that handle these distinct patterns together — it cannot be done without modifying the attention computation.

Why:

Prefill and decode have fundamentally different attention computation patterns. Prefill computes attention as a matrix-matrix operation — all prompt tokens attend to one another in a single parallel pass — while decode computes attention as a matrix-vector operation: a single newly generated token attends to all cached keys and values in the KV cache. Mixing the two phases in one batch (as vLLM does with chunked prefill / mixed batching) requires specialized attention kernels that handle variable-length sequences with different roles (some doing full prefill attention, others doing single-token decode attention) within the same forward pass. The claim that 'no modification to the attention computation is needed' is therefore false; production serving systems explicitly redesign the attention kernel to support this mixing.

AI Engineering/ai-production/streaming

You are the lead AI infrastructure engineer at a company building a multi-tenant LLM gateway. The gateway proxies requests to multiple model providers (OpenAI, Anthropic, and self-hosted vLLM replicas) and must stream generated tokens back to end-user browser clients in real time.#

Show answer

Streaming Architecture for a Multi-Tenant LLM Gateway

Transport & Event Framing (c1)

Browser clients connect via Server-Sent Events (SSE) over HTTPS. SSE is chosen over WebSocket because the data flow is unidirectional (server→client) for token streaming, SSE works through all standard HTTP infrastructure (CDNs, reverse proxies, load balancers) without protocol upgrades, and the browser EventSource API provides built-in reconnection with Last-Event-ID. For clients needing bidirectional communication (e.g., audio input for speech models), a WebSocket upgrade path is available but is not the default.

Each SSE frame is a JSON object with a type discriminator:

event: delta
data: {"type":"text_delta","content":"Hel","index":42,"request_id":"req_abc"}

event: delta
data: {"type":"tool_call_delta","tool_call_id":"call_1","arguments":"{\"loc"}

event: usage
data: {"type":"usage","prompt_tokens":120,"completion_tokens":43,"model":"gpt-4o"}

event: done
data: {"type":"done","request_id":"req_abc"}

The gateway assigns a monotonically increasing id: field (SSE event ID) to every frame so the browser's EventSource automatically sends Last-Event-ID on reconnection. The index field gives the client a per-request token sequence number for gap detection.

Backpressure & Upstream Cancellation (c2)

Each client connection has a bounded write buffer (e.g., 64 KB ring buffer) on the gateway. When the gateway reads a token from the upstream provider stream, it attempts to write it to the client SSE connection with a write deadline of 2 seconds. If the client is not reading (flaky mobile connection), the write blocks until the buffer fills or the deadline expires:

  1. If the buffer is full or the write deadline fires, the gateway closes the client connection and marks the stream as cancelled.
  2. The gateway then propagates cancellation upstream: it calls abort() on the fetch Response body (for HTTP-based providers like OpenAI/Anthropic) or cancels the gRPC client stream (for vLLM's gRPC interface). This causes the provider to stop generating tokens and free the KV-cache slot on the GPU.
  3. For self-hosted vLLM, the gateway sends an HTTP request abort, which vLLM's async streaming handler catches, cancels the generation task, and releases the GPU memory.

The key invariant: a stalled client never causes unbounded memory growth on the gateway, and a cancelled stream always frees upstream GPU resources within seconds.

Stream Resumability & Idempotency (c3)

The gateway maintains a short-lived append log per request backed by Redis Streams (TTL: 5 minutes). Every token frame forwarded to the client is also appended to the Redis stream with its sequential event ID. The full generated output is thus persisted server-side for the TTL window without re-invoking the model.

On reconnection:

  1. The browser EventSource reconnects automatically and sends Last-Event-ID: 41.
  2. The gateway looks up the request by request_id, reads events 42+ from the Redis stream, and replays them to the client.
  3. If the upstream generation is still in progress (the client disconnected but the gateway did not cancel upstream — see below), the gateway continues forwarding live tokens after the replayed ones.
  4. If the generation already completed, the gateway replays all remaining buffered events and sends done.

Critical design decision: when a client disconnects, the gateway does NOT immediately cancel the upstream. Instead, it enters a grace period (e.g., 10 seconds) during which it continues consuming the upstream stream into the Redis log. If the client reconnects within the grace period, it gets a seamless resume. If the client does not reconnect within the grace period, the gateway cancels the upstream to free resources. This trades a small amount of wasted GPU time for a much better reconnection UX on flaky networks.

Idempotency: the request_id and monotonic event IDs ensure the client can deduplicate any replayed frames. The client applies tokens by index, so out-of-order or replayed frames are handled gracefully.

Multi-Provider Race & Partial Failure (c4)

For the speculative fan-out scenario, the gateway opens two upstream SSE streams concurrently. The design is first-token-wins with immediate loser cancellation:

  1. Both upstream streams are opened. The gateway holds a mutex on the client-facing stream.
  2. The first provider to emit a text_delta acquires the mutex and becomes the winner. Its stream is forwarded to the client.
  3. The gateway immediately aborts the losing provider's stream (closes the HTTP connection, cancels the gRPC stream) to stop its generation and free resources.
  4. Race handling: if the losing provider emits a token between the winner's first token and the abort being processed, that token is silently discarded — it is never forwarded to the client. The mutex ensures only one provider's deltas reach the client at a time.
  5. Partial failure: if the winning provider's stream errors mid-generation, the gateway sends an error event to the client ({"type":"error","code":"provider_error","message":"..."}) followed by closing the connection. The client can retry. The gateway does NOT silently switch to the losing provider (which was already cancelled and may have partially consumed GPU resources).
  6. If both providers fail before any token is emitted, the gateway returns an error to the client without any deltas.

The client never receives interleaved or duplicated deltas because the mutex + winner-takes-all design guarantees a single source of truth for the duration of the stream.

Streaming Observability & Real-Time Cost (c5)

Metrics are emitted via OpenTelemetry spans and Prometheus histograms:

  • TTFT: a span is started when the gateway receives the client request. The span's first child event is recorded when the first text_delta is forwarded to the client. TTFT = first_delta_time - request_received_time. This is exported as a Prometheus histogram (llm_gateway_ttft_seconds) labeled by model, provider, and tenant.
  • Inter-token latency (ITL): the gateway records the wall-clock delta between consecutive text_delta forwards. Exported as a histogram (llm_gateway_inter_token_latency_seconds) with the same labels.
  • Throughput: tokens/sec computed at stream close as total_tokens / (stream_end - first_delta_time).

Cost attribution operates in two phases:

  1. Real-time estimation: the gateway maintains a running token counter per request, incremented on each text_delta. It multiplies this by the model's per-token rate (from a pricing config table) and adds the prompt cost (known at request time). This running estimate is checked against the tenant's spend limit on every token. If the estimate exceeds 90% of the limit, the gateway sends a warning event to the client; at 100%, it cancels the stream and returns a spend_limit_exceeded error.
  2. Final reconciliation: when the upstream stream closes, the provider sends a final usage event with the exact token counts. The gateway computes the true cost, writes it to the billing ledger (attributed to the tenant via the request context), and adjusts the tenant's spend counter. Any discrepancy between the estimate and the actual is reconciled here.

For the spend-limit overshoot problem: the gateway applies a safety margin (e.g., 95% of the limit triggers cutoff) so that even with estimation jitter, the actual cost rarely exceeds 100%. When it does (because the final usage event reveals more tokens than estimated), the overage is logged and the tenant's next request is rejected until the balance is settled. This is a deliberate trade-off: hard real-time enforcement would require provider-side pre-checks that don't exist in the streaming API, so a small, bounded overage is accepted.

Why:

This is a staff-level AI engineering design problem requiring the candidate to reason about five distinct production concerns in a streaming LLM gateway: transport protocol selection with structured event framing, backpressure with upstream cancellation propagation to free GPU resources, stream resumability without regeneration via a server-side replay log, multi-provider race conditions with first-token-wins semantics, and streaming observability with two-phase cost attribution. Each concern has a concrete correct answer that can be evaluated against the rubric criteria.

AI Engineering/ai-production/streaming

You are the tech lead for an AI platform that serves an autoregressive LLM to 2,000+ concurrent enterprise users. Users expect server-side token-by-token streaming (first token in <800 ms, subsequent tokens at >40 tokens/s per stream). The underlying model uses continuous batching (e.g., vLLM/TGI-style iteration-level scheduling) on a shared GPU cluster.#

Show answer

I would expose the model through an HTTP/2 API gateway that delivers tokens via Server-Sent Events (SSE). SSE is unidirectional (server-to-client), which matches our use case since the prompt is sent once in the initial POST and only tokens flow back. Each token is framed as a data: line carrying a JSON object with fields like {"token": "...", "index": N, "request_id": "...", "finish_reason": null}, terminated by a data: [DONE] sentinel. SSE gives us automatic browser-level reconnection via the Last-Event-ID header, and HTTP/2 multiplexing avoids head-of-line blocking across concurrent streams. I would avoid WebSocket here because we don't need client-to-server frames after the initial request, and the extra connection-state management isn't justified.

On the server side, the API gateway forwards each request to a continuous-batching engine (like vLLM). The engine runs an iteration loop: each step, it selects a batch of active requests, runs a single forward pass, and produces one new token per request. Each active request holds a set of KV-cache blocks (managed via paged attention at the block level, not contiguous allocation). When a new request arrives, the scheduler allocates KV-cache blocks for its prefill, runs prefill (possibly batched with other prefills), then admits it to the decode batch. When a request finishes (EOS or max_tokens), its blocks are freed and it exits the batch. This means requests at different generation stages — some on token 5, some on token 200 — coexist in the same forward pass, and the batch composition changes every iteration. The gateway receives each token from the engine and pushes it into a bounded per-connection channel for that client's SSE stream.

Backpressure is the critical design point. Each client connection gets a bounded output buffer (a fixed-capacity queue, say 64 tokens). Under normal operation, the gateway writes tokens into this queue and the SSE writer drains it to the client. If a client reads slower than the GPU generates tokens, the queue fills up. When the queue is full, I suspend that request in the scheduler: the engine marks it as inactive (removes it from the decode batch for the next iteration) but retains its KV-cache blocks so it can resume without recompute. The request stays suspended until the client drains enough tokens to open space, at which point it's re-admitted to the batch. If a client remains suspended beyond a configurable deadline (e.g., 30 seconds), I terminate the stream entirely, free the KV-cache, and return an error — this prevents a permanently-stuck consumer from hoarding GPU memory. This design ensures a slow consumer only stalls its own generation; the GPU continues serving other requests in the batch at full throughput. The trade-off is that suspending a request wastes its KV-cache memory while idle, so I'd set the suspension capacity as a fraction of total cache and evict with recompute if under memory pressure.

On failure: if the connection drops mid-generation, the gateway detects the closed socket (either via a write failure or a keepalive timeout), immediately removes the request from the batching engine's active set, and frees its KV-cache blocks. Generation does NOT continue server-side — there's no point spending GPU cycles for a client that isn't there. The client observes an abrupt stream termination. Because the model is stateless across requests, there is no automatic server-side resume. The client must replay the request with the same prompt; to avoid redundant computation, the server's prefix cache (if enabled) will recognize the prompt prefix and skip re-prefilling the shared portion. The client should include a unique request_id; if it reconnects with SSE's Last-Event-ID carrying the last token index it received, the gateway can (best-effort, not guaranteed) resume from that point if the KV-cache hasn't been evicted — but this is an optimization, not a correctness guarantee. The guaranteed contract is: either the client receives a complete response (terminated by [DONE]) or it receives a partial response followed by an error/disconnect, in which case it must replay. Deduplication of tokens on replay is the client's responsibility using the token index field.

Why:

This is a staff-level design question because it requires integrating four subsystems — wire protocol, continuous-batching scheduler internals, per-connection backpressure with KV-cache implications, and failure/resumability semantics — and making them coherent. A strong answer must demonstrate knowledge of iteration-level scheduling, paged KV-cache management, bounded-buffer backpressure that isolates slow consumers, and honest failure semantics that distinguish guaranteed from best-effort behavior.

AI Engineering/ai-production

You are building the inference serving platform for a company that deploys a 70B-parameter decoder-only LLM (80 transformer layers, GQA with 8 KV heads, head_dim 128, FP16 weights ≈ 140 GB). The system must meet these SLOs:#

Show answer

Architecture Overview

1. Iteration-Level Continuous Batching

Each GPU worker runs an iteration-level scheduler (as in vLLM/TGI). At every decode step (every ~20–40 ms), the scheduler:

  • Evicts sequences that have emitted EOS or hit max tokens.
  • Admits queued requests up to the limit imposed by available KV cache blocks.
  • Issues a single fused forward pass for the entire active batch.

This contrasts with static batching, where a batch is formed at request time and all slots are occupied until the longest sequence finishes — leaving GPU capacity idle on short sequences. Continuous batching keeps the batch full at every step, maximizing the arithmetic intensity of the decode phase (which is memory-bandwidth bound at batch size 1 but improves as batch size grows because weight loads are amortized).

2. KV Cache Memory Management (PagedAttention)

The KV cache is the primary throughput constraint. Per-sequence KV cache size for this model:

  • 80 layers × 2 (K+V) × 8 KV heads × 128 head_dim × 2,048 tokens × 2 bytes (FP16) = 80 × 2 × 8 × 128 × 2,048 × 2 ≈ 6.7 GB per sequence.

Across 5,000 concurrent sessions, the total KV cache demand is enormous (tens of TB), far exceeding GPU HBM. We use PagedAttention: the KV cache is divided into fixed-size blocks (e.g., 16 tokens/block). A block table maps each sequence's logical block indices to non-contiguous physical HBM blocks. This eliminates internal fragmentation (sequences only allocate the blocks they need) and enables prefix sharing: sequences with identical system prompts share the same physical KV cache blocks via copy-on-write reference counting. On 8× H100 nodes (640 GB HBM total), we can hold roughly 640 GB ÷ 6.7 GB ≈ 95 concurrent full-length sequences per node — so we need horizontal scaling across many nodes, each running replicas.

3. Prefill/Decode Disaggregation

Prefill is compute-bound (large GEMMs, high arithmetic intensity); decode is memory-bandwidth bound (small per-token GEMMs). Mixing a long prefill with active decodes in the same iteration stalls the decode batch: the GPU spends 200+ ms on the prefill while every active decode token waits, blowing the 50 ms inter-token SLO.

I disaggregate into two worker pools:

  • Prefill workers (TP=8 within a node): handle prompt encoding, produce KV cache, then transfer it to decode workers.
  • Decode workers (TP=8): run continuous batching over active sequences only.

Alternatively, chunked prefill splits a long prompt into chunks (e.g., 512 tokens) and interleaves each chunk as one "sequence" in the continuous batching loop, so no single prefill monopolizes an iteration. This is simpler operationally and I'd start with chunked prefill, moving to full disaggregation if TTFT SLOs are not met.

4. SLO-Aware Autoscaling and Admission Control

The admission controller estimates per-request TTFT (based on prompt length, current queue depth, and prefill worker utilization) before enqueuing. If the estimate exceeds 800 ms, the request is either queued (with a position estimate returned to the client) or rejected with an HTTP 429 + Retry-After.

Autoscaling is keyed on KV cache block utilization and estimated TTFT distribution, not raw GPU utilization. When KV cache utilization exceeds 80% or p90 TTFT exceeds 600 ms, the scaler provisions new replica groups. For bursty traffic, I maintain a warm pool of 2 idle replica groups (pre-loaded model weights, zero active sequences) that can absorb a 10× spike within seconds. Predictive scaling uses historical traffic patterns to pre-warm additional groups 15 minutes before expected spikes.

Graceful degradation under overload: shed load by (a) reducing max output tokens per request, (b) routing overflow traffic to a smaller fallback model (e.g., 13B), and (c) returning 429s with backoff headers.

5. Model Parallelism and Fault Tolerance

The 140 GB model requires TP=8 within a single H100 node (NVLink provides 900 GB/s inter-GPU bandwidth, minimizing all-reduce latency). With 8 nodes, we run 8 independent TP replicas, each serving ~95 concurrent sequences → ~760 concurrent sessions; further horizontal scaling (more nodes, more replicas) handles the 5,000-session target.

Pipeline parallelism (PP) is used only if a single replica cannot fit the model within one node's HBM — for a 70B model in FP16 (140 GB) across 8×80GB GPUs (640 GB), TP=8 within one node suffices. PP would be needed for larger models (e.g., 405B).

Fault tolerance: each TP replica group runs a health check (heartbeat + dummy inference every 5 seconds). On GPU failure within a TP group, the entire group is marked unhealthy; the load balancer stops routing to it, and in-flight requests on that group fail and are retried on a healthy replica (with the original prompt re-prefilled; KV cache for in-progress decodes is lost). A replacement group is launched from the warm pool. At 99.9% availability, we tolerate occasional request retries — the client SDK uses idempotent request IDs to handle duplicates.

Putting It Together

Client → API Gateway (rate limit, auth)
       → SLO-Aware Router (estimates TTFT, routes to replica)
       → Replica Group (TP=8, H100 node)
           → Chunked-Prefill Scheduler (interleaves prefill chunks with decode)
           → PagedAttention KV Cache Manager (block allocation, prefix sharing)
           → Continuous Batching Engine
       ← Streaming SSE response

Autoscaler watches KV cache util + TTFT p90 → scales warm pool
Why:

This is an open-ended design exercise scored against five weighted criteria: (c1) iteration-level continuous batching vs static batching, (c2) KV cache as the primary throughput bottleneck with paged/block allocation and prefix sharing, (c3) prefill/decode disaggregation or chunked-prefill scheduling to protect inter-token latency, (c4) SLO-aware admission control and autoscaling keyed on KV cache pressure rather than generic GPU utilization, and (c5) tensor parallelism within a single node with a justified size comparison showing pipeline parallelism is unnecessary for 140 GB across 640 GB HBM, plus replica-level failover. The sample answer addresses every criterion.

Related interview questions

Job market

See ai-engineering salaries and hiring demand from live job postings.

The other 3 questions

This page shows 25 and marks what you pick. That's as far as a page can go. A free account opens the other 3 and keeps every answer. What you miss comes back until it's right: after a day, then at longer gaps.

Start with this topic

Free · the whole bank · 100 marked answers per 30 days · written feedback on the paid plan

What moved, monthly

One email a month when the bulletin comes out: what moved in the markets we track, and the new question topics we published. Confirm your address to join. Unsubscribe any time.