Vibe Engines
YouTube

Vibe Engines is free, interactive AI engineering & system design — runs in your browser. Pick a learning path

AI System Design

Design Realtime Speech Translation

Step 1 / 9

Learn AI system design by building a live speech-to-speech translator like a conference interpreter earpiece or Google's live translation — streaming ASR, MT, and TTS chained under a tight latency…

The whole design, in writing

Every step of the build above, written out: the problem each piece solves, the option that was taken and the ones that were not, the numbers, and how it fails in production.

The big idea

A conversation, not a walkie-talkie

Chain speech-to-text, translation, and text-to-speech together and you have SOMETHING — but "wait for the speaker to finish, then translate the whole thing, then speak it" produces a multi-second lag per exchange that makes a real back-and-forth conversation feel like a walkie-talkie: talk, wait, listen, wait, repeat.

Speakertalking, live
New in this step: Speaker.

We'll build it to actually stream: partial transcripts, incremental translation that revises itself as more context arrives, careful chunking, voice-preserving synthesis, and an explicit latency budget split across every stage.

What the new pieces do

Speakerclient
Someone speaking naturally, in real time, expecting the translation to keep pace with a real conversation, not lag behind it.

Step 1 · The baseline

Record, then translate, then speak

Simplest version: wait for the speaker to finish a full utterance, transcribe it, translate the complete text, synthesize speech, then play it. What's the actual cost of waiting for completeness?

Streaming ASRIncremental MTStreaming TTSListener
New in this step: Streaming ASR, Incremental MT, Streaming TTS, Listener. · swipe to pan the diagram

The pipeline waits for a full utterance before doing anything. What's the cost?

  1. Accuracy from having full context is real, but it's bought at the cost of the ENTIRE utterance's length plus processing time before the listener hears anything — for a long sentence, that's multiple seconds of dead air, incompatible with real conversational pacing.

  2. Waiting for completeness means the listener's first sound arrives only after the ENTIRE pipeline has processed the ENTIRE utterance — for anything but very short sentences, that delay breaks the natural overlap and pacing of real conversational turn-taking.

  3. Even ordinary conversational sentences (5-10 seconds of speech) produce a noticeable, conversation-breaking delay under this approach — it's not just a long-speech problem.

Waiting for a complete utterance stacks its full length plus every stage's processing time before the listener hears anything — for real conversational pacing, that delay is disqualifying. The entire pipeline needs to stream, producing partial output as input arrives, not batch-process complete utterances.

Why this piece earns its place

Streaming is not a faster version of the batch pipeline — it changes what every stage is. A batch stage is a request: complete input in, complete output out, retryable, load-balanced per call, drained cleanly on deploy. A streaming stage is a long-lived session holding audio state, so the connection is sticky, an autoscaler cannot move it mid-utterance, and a rolling restart cuts people off mid-sentence unless you learn to drain conversations rather than instances. Retries get worse too: a failed batch call re-runs invisibly, but you cannot retry a chunk the listener already heard, so recovery becomes a correction rather than a repeat. Capacity accounting changes shape — a slot is held for as long as the session is open, not for the length of one call to a model, so utilization tracks connected time rather than request rate. Silence costs exactly what speech costs, which means a sparse meeting and a dense one occupy identical capacity while producing wildly different amounts of translated speech, and the number you provision against is concurrent conversations, not traffic. The one case to skip all of this: nobody is waiting. Dubbing recorded video or translating voicemail has no live listener, and there batch buys better quality, simpler operations and cheaper compute. Paying for streaming with no one on the other end is pure complexity.

What the new pieces do

Streaming ASRgateway
Converts speech to text incrementally, emitting partial transcripts as words arrive, not waiting for the speaker to finish.
Incremental MTservice
Translates partial transcripts as they arrive — necessarily working with incomplete information, which it will need to revise.
Streaming TTSservice
Synthesizes the translated text into speech, ideally preserving something of the original speaker's voice and delivery.
Listenerclient
Hears the translated speech and needs it to feel close enough to real time to sustain an actual conversation.

Step 2 · Streaming ASR

Partial transcripts, not one final answer

Fix the biggest lag source first: don't wait for the speaker to finish before transcribing.

SpeakerStreaming ASRIncremental MTStreaming TTSListener
The system as it stands at this step. · swipe to pan the diagram

Should transcription wait for the speaker to pause or finish, or produce output continuously?

  1. Waiting for pauses still reintroduces meaningful per-segment latency, and natural conversational speech often has few clean pauses within a longer thought — you'd still be waiting a long time in many real utterances.

  2. Streaming ASR treats transcription as a continuously-updating process rather than a single final answer — early partial text may be revised as more context arrives, but something is always available to feed downstream immediately, which is what unlocks the rest of the pipeline streaming too.

  3. Fixed time-based chunking with no regard for actual speech boundaries can cut words or short phrases mid-way, producing transcription errors that have nothing to do with the audio quality — chunking needs to be informed by the audio, not arbitrary.

Make ASR streaming: it continuously emits partial transcripts as audio arrives, revising earlier words if later context changes the most likely interpretation. This gives downstream translation something to work with immediately, rather than only after the speaker stops.

Why this piece earns its place

What makes partials usable downstream is a stability marker — ASR saying which prefix it considers committed and which it may still rewrite. Without one, every consumer guesses. The translator re-translates text that was never going to change, burning compute on identical input, and the renderer has to treat the whole line as provisional, repainting settled words alongside the tail that is genuinely still moving. That indiscriminate repaint is the flicker, and it is the failure nobody tracks: word error rate is measured on the final transcript and looks excellent while the partials thrash, because the listener watched every draft and the metric never saw one of them. With the marker, churn is scoped to the words still in play, which is what lets the next stage hold captions and audio to different release rules at all. Two operational notes worth having ready. Streaming decoding re-runs the model over overlapping audio, so compute per second of speech is a multiple of the batch cost for the same words — that lands as GPU spend, not as latency, which is why it survives a latency review. And partials usually arrive without punctuation or casing, since restoring those is a separate model, which matters here because the next stage segments on exactly those cues. Whether that model runs on partials or only on committed text is the choice that sets how much of the pipeline is allowed to churn.

Step 3 · Incremental translation must revise itself

Word order isn't the same across languages

Translating a partial sentence before it's complete forces the translator to guess. In German, the main verb often comes at the END of a clause — translating the first few words into English before seeing that verb can require a fundamentally different sentence structure than what was guessed. What should happen?

Incremental MTrevises with stabilizerStreaming TTSvoice-preservingListenerhears the translationRe-translation Stabilizerrevises as context grows
New in this step: Re-translation Stabilizer.

A partial translation was based on an early guess that later context contradicts. What should happen?

  1. Locking in an early, possibly-wrong guess (especially for languages with meaningfully different word order) guarantees the FINAL translation is sometimes simply incorrect — accuracy has to matter more than never revising, or the whole point of translation is undermined.

  2. Because early-arriving words genuinely may not determine the correct translation until more context (like a German sentence-final verb) arrives, the system needs to treat its own partial output as provisional and revise it — the alternative is either high latency (wait for certainty) or wrong output (never revise).

  3. This just reverts to Step 1's full-utterance-latency problem — the whole point of streaming translation is to start producing SOME output before the sentence is complete, while accepting that output may need revision.

Add a Re-translation Stabilizer: as more of the sentence arrives, it re-evaluates whether the current translation is still the best one and revises the displayed or spoken output if a meaningfully better translation is now possible — accepting a degree of self-correction as the necessary cost of low latency, rather than either waiting for certainty or locking in an early wrong guess.

Why this piece earns its place

Revision is cheap in pixels and impossible in audio. A caption can be rewritten in place; a spoken sentence has already left the room and cannot be un-said. So the Stabilizer does not have one output policy, it has two — captions take revisions as they land, repainting only the unstable tail that Step 2's marker already scopes for them, while the audio path is handed nothing until the Stabilizer has committed it. That single decision splits the system into two clocks: the caption track runs ahead, the voice track runs behind by however long the commit rule withholds text, and you have to say which one the latency budget is measured against or the number means nothing. Two things fall out of it. Every emitted segment needs a revision id so the renderer replaces rather than appends — a naive append turns each correction into visible duplicate text. And the rewrite window has to be bounded: how far back may a late word reach? Unbounded means the Stabilizer holds the whole utterance and its work grows with sentence length. Bounded means some genuinely correct late fixes are dropped on purpose, and that is the honest trade.

What the new pieces do

Re-translation Stabilizerservice
Re-translates a segment as more of the sentence arrives, since word order across languages can make an early partial translation wrong.

Step 4 · Chunking policy

Not word-by-word, not full-sentence

How large should each chunk of text handed to translation be — every single word the instant it's transcribed, or wait for a reasonably complete phrase?

SpeakerStreaming ASRIncremental MTStreaming TTSListenerRe-translation Stabilizer
The system as it stands at this step. · swipe to pan the diagram

What's the right granularity for feeding transcribed text into the translator?

  1. Word-by-word translation, with essentially zero context per word, produces extremely poor and constantly-revising output — most words' correct translation genuinely depends on surrounding words, which single-word chunks don't provide.

  2. Phrase-level chunking gives the translator enough local context to produce a reasonably confident translation while still emitting output well before the full sentence completes — a deliberate middle ground between the two extremes, tuned to the actual latency/quality trade-off.

  3. This reverts to Step 1's core latency problem — sentence-level chunking defeats the purpose of streaming translation entirely.

Chunk at natural phrase or clause boundaries — enough accumulated context for a reasonably confident translation, without waiting for full-sentence completeness. This is a tunable policy sitting deliberately between word-by-word (too little context, too much revision) and full-sentence (too much latency).

Why this piece earns its place

A phrase boundary is not handed to you — something has to detect it, on partial transcripts that are themselves still being revised, and that detector is what really governs latency variance. Two consequences get missed. First, the tail: a speaker who trails off, or whose last words never satisfy the boundary rule, leaves a chunk that is never flushed, so the end of that utterance is simply never translated. Average latency looks healthy, because a missing chunk produces no slow measurement — it produces no measurement at all. You need a timeout that force-flushes a stale buffer, and that timeout is part of the policy, not an implementation detail someone adds later. Second, chunk size is not the same dial as context, and they get conflated constantly. Shrink the chunk and the translator does not see less; it sees the same words arriving as history rather than as input, so it re-encodes a growing prefix on every emission and cost per second of speech climbs the longer someone talks without stopping. How much prefix to carry, and when to drop it, is a second decision — and when latency drifts late in a long turn, it is usually that one, blamed on the first. Boundaries also become the unit of recovery — when a stage dies you replay from the last one — so smaller chunks make failure cheaper as well as making output earlier.

Step 5 · Voice-preserving synthesis

Should the translation sound like the speaker?

The translated text needs to become speech. Should the synthesized voice be a generic, neutral voice, or attempt to preserve something of the original speaker's actual voice and delivery?

SpeakerStreaming ASRIncremental MTStreaming TTSListenerRe-translation Stabilizer
The system as it stands at this step. · swipe to pan the diagram

Synthesizing the translated speech — generic neutral voice, or preserve the original speaker's characteristics?

  1. A fixed generic voice strips away speaker identity and emotional delivery (urgency, warmth, humor) entirely — for many real use cases (a video call, a live-translated talk), that loss meaningfully degrades the experience of hearing someone specific rather than a robotic intermediary.

  2. Voice-preserving TTS carries over pitch, tone, and delivery style from the original speaker, so listeners hear something closer to "this specific person, speaking your language" rather than an anonymous narrator reading a translation — a meaningfully richer, more natural experience for live conversation or presentation use cases.

  3. Voice-preserving synthesis is valuable precisely in LIVE settings (video calls, live interpretation) where speaker identity matters most to the listener — restricting it to pre-recorded content misses the primary use case.

Use voice-preserving Streaming TTS that captures elements of the original speaker's prosody and vocal characteristics, so the synthesized translation sounds like a translated version of that specific person rather than a generic narrator — while still meeting the same streaming, low-latency requirement as every other stage.

Why this piece earns its place

Translated speech is rarely the same length as the source, and that is the problem voice preservation quietly inherits. Preserve the speaker's rate faithfully and the synthesized audio drifts further behind with every sentence, until the translation is still talking after the speaker has stopped. Compress it to stay aligned and you have altered the delivery you went to the trouble of preserving. So the synthesizer needs a rate controller wired to the latency budget, and it needs to be told which to give up — pacing fidelity or synchrony — because it cannot hold both when the two languages disagree about length. Two operational points belong with it. A voice-cloning path on a live pipeline is also a voice-spoofing path, so enrollment, consent and some form of output marking are product requirements, not compliance work bolted on after launch. And plan the fallback: when there is not enough clean audio to model a voice, assign a stable voice per speaker for the session rather than picking one at random, so a listener on a multi-party call is not relearning who is who on every turn.

Step 6 · Filter disfluencies before translating

Real speech is messy

Real spoken language is full of "um," false starts, and self-corrections ("I think— actually, I mean..."). Feeding that directly into a translator produces garbled, unusable output.

Streaming ASRpartial transcriptsIncremental MTrevises with stabilizerDisfluency Filterstrips ums, false starts
New in this step: Disfluency Filter.

Add a Disfluency Filter between ASR and translation that strips filler words and resolves self-corrections (keeping the corrected version, discarding the abandoned false start) before the text reaches translation — which assumes reasonably well-formed input and produces much better results on cleaned text than on raw, messy transcription.

Why this piece earns its place

Deleting words breaks alignment, and alignment is what half this pipeline runs on. Caption timing, the Stabilizer's sense of how much context it holds, and any per-stage latency measurement all key off where in the audio a word sat, so the filter has to emit spans — what it kept, what it removed and from where — not just a cleaner string. The sharper constraint is that ASR revises. The filter therefore runs repeatedly over a prefix that keeps changing, and it has to be stable under that: if correcting one word flips an earlier keep-or-drop decision, the change propagates into a translation revision the Stabilizer then has to absorb, and a single ASR correction becomes a visible rewrite of a whole clause. Make it a function of the committed prefix only. Budget, too, for filler patterns being language-specific — the cheap rule-list version does not transfer, so every new source language quietly ships a new lexicon. Skip the filter on prepared speech; it earns its keep on unscripted, hedging conversation.

What the new pieces do

Disfluency Filterservice
Cleans real, messy spoken language — filler words, self-corrections, restarts — before it reaches translation, which assumes reasonably well-formed input.

Step 7 · Budget the latency across every stage

No single stage should eat the whole budget

There's a total acceptable end-to-end latency for the experience to feel real-time. How should that budget be allocated across ASR, translation, and synthesis?

Incremental MTrevises with stabilizerStreaming TTSvoice-preservingDisfluency Filterstrips ums, false startsLatency Budgetper-stage allocation
New in this step: Latency Budget.

Define an explicit Latency Budget split across stages — e.g. a target of a few hundred milliseconds each for ASR, translation, and synthesis — so no single stage is free to silently consume the whole acceptable delay. Each component is engineered and monitored against its OWN slice of the budget, not just an aggregate end-to-end target that doesn't reveal which stage is actually the bottleneck.

Why this piece earns its place

Write the budget as an average and you will miss how this pipeline actually fails. The stages are serial and stateful, so a chunk that overruns does not cost one slow chunk — it pushes everything behind it back, and because speech keeps arriving at the speaker's pace, the lag never recovers inside that turn. Tail latency here is not a percentile over independent requests; it is accumulated, permanent drift. That makes the missing half of the budget a policy: when a stage is out of time, does it emit the lower-quality result it already has, or block and stay correct? Someone has to own that answer per stage, and it is a different answer for recognition than for synthesis. What a stage split omits is everything that is not a stage. Network time, device audio buffering and the playback jitter buffer all sit inside the listener's experience and outside every box on this diagram, so no team owns them and no dashboard shows them. Every stage can hit its slice and the call can still feel late, and the conversation that follows is always the same one: each team is green, so the problem must be somewhere else. Give the client-side slice a number and a name, even if the name is yours, or the budget only ever covers the part that was easy to measure.

What the new pieces do

Latency Budgetdata
The total acceptable end-to-end delay, split explicitly across ASR, translation, and synthesis so no single stage silently eats the whole budget.

Step 8 · The sharp edges

Code-switching and knowing when someone's actually done

Two real-world complications: a speaker who switches languages mid-sentence, and correctly distinguishing a mid-thought pause from the actual end of an utterance.

SpeakerStreaming ASRIncremental MTStreaming TTSListenerRe-translation StabilizerDisfluency FilterLatency Budget
The system as it stands at this step. · swipe to pan the diagram

For code-switching, per-segment language detection (not one fixed language assumption for the whole session) lets the pipeline re-route a switched segment through the correct language pair rather than force-translating it as if it were still the original language. For end-of-utterance detection, use a combination of silence duration, intonation cues, and semantic completeness (does this look like a finished thought) rather than silence alone — a thoughtful pause shouldn't be mistaken for "done speaking" and trigger a premature finalization.

Why this piece earns its place

End-of-utterance is the only place this system commits, and its two errors are not symmetric. Fire early and you truncate a thought the listener has already heard spoken, so the repair arrives as an audible correction. Fire late and you add dead air, which is the exact failure the whole design exists to remove. The signals feeding that call also disagree about how fresh they are. Silence is free and true the instant it happens. Intonation comes off audio frames you already hold. Semantic completeness needs the transcript, so it arrives a stage late and is read from text that may still be revised under it. Combine them and the decision is only ever as current as the slowest input, which means the signal you add to stop premature cuts spends part of the dead air it was added to protect. Log which of the three actually carried a given call, or you will spend a week tuning a threshold that was not the thing deciding. On code-switching, the trap is trusting a classifier on a very short segment. Detection over a phrase is unreliable, so weight it with a prior: the session's declared pair, and what this speaker has done so far. A confidently wrong route is debugged in the wrong place, because it looks like a bad translation rather than a routing decision.

You did it

You just designed a realtime translation system.

SpeakerStreaming ASRIncremental MTStreaming TTSListenerRe-translation StabilizerDisfluency FilterLatency Budget
The finished design, end to end. · swipe to pan the diagram

Everything you assembled, in order

  • Waiting for a complete utterance stacks additive latency across every stage — real conversational pacing requires the whole pipeline to stream.
  • Streaming ASR emits continuously-updating partial transcripts rather than one final answer, unlocking everything downstream.
  • Incremental translation must be willing to REVISE its own output as more context arrives — word order genuinely differs across languages.
  • Chunking at phrase/clause boundaries balances context quality against latency, between word-by-word and full-sentence extremes.
  • Voice-preserving synthesis carries speaker identity and delivery through translation, not just literal words.
  • A disfluency filter cleans messy real speech before it reaches translation, which assumes reasonably well-formed input.
  • An explicit per-stage latency budget gives each component a concrete target, rather than one opaque aggregate goal.
  • Code-switching and end-of-utterance detection both need dedicated handling beyond a single fixed-language, silence-only assumption.

Where an interviewer pokes next

Getting the boxes right is the easy half. These are the questions that separate a candidate who drew the diagram from one who has run the thing. Answer each one out loud before you open it.

  1. Why is this a genuinely different design from separately-built Speech-to-Text and Text-to-Speech systems?

    Those systems each optimize one stage in isolation — ASR for transcription accuracy and its own latency, TTS for natural-sounding synthesis of given text. This design is specifically about the CHAINING of three such stages (with machine translation between them) under one tight, shared, end-to-end latency budget, plus problems that only exist BECAUSE they're chained: incremental translation needing to revise itself as ASR corrects earlier words, and voice characteristics needing to flow from the original speaker through translation into synthesis. The interesting engineering here is specifically at the SEAMS between components, not any one component alone.

  2. Why not just always wait a fixed extra 500ms buffer before finalizing each translation, to reduce how often it needs to revise?

    A fixed buffer trades latency for revision-reduction UNIFORMLY, even for chunks where the early translation was already correct and didn't need the extra wait at all — you'd be paying the delay cost on every chunk to reduce revisions on only some of them. The Stabilizer's adaptive approach (revise only when new context ACTUALLY changes the best translation) pays the revision cost only when genuinely needed and stays fast otherwise — a fixed buffer is a blunter, less efficient version of the same underlying trade-off the Stabilizer already makes dynamically.

  3. How would voice-preserving synthesis need to change for a language pair with very different phoneme inventories (sounds that don't exist in the target language)?

    Voice preservation transfers characteristics LIKE pitch range, speaking rate, and intonation pattern — properties that exist independent of which specific phonemes make up the words — so a target language lacking certain sounds present in the source doesn't block preserving those higher-level vocal characteristics; the synthesized speech still uses the TARGET language's actual phoneme inventory (correctly pronounced target-language words) while carrying over the SOURCE speaker's pitch, pacing, and emotional delivery style onto that correctly-pronounced output. The two concerns — which sounds are used, and how they're delivered — are largely separable.

  4. What determines how large the phrase-level chunks in Step 4 should actually be — is this the same for every language pair?

    No — the right chunk size is influenced by the SOURCE language's grammatical structure specifically. A language where word order closely matches the target language (say, translating between two similarly-structured languages) can often translate confidently on smaller chunks, since less restructuring is needed. A language pair with meaningfully different structure (like German-to-English's verb-position difference from Step 3) may need larger chunks — or a higher tolerance for revision — to reach the same translation confidence, since critical structural information (like where the verb goes) arrives later in the source sentence. Chunk size is a tunable parameter that reasonably differs per language pair.

  5. Could the disfluency filter accidentally strip something that was actually meaningful, not just filler?

    This is a genuine precision/recall trade-off worth naming: an aggressive filter risks removing a hesitation or repetition that was actually meaningful (a stutter that's part of someone's natural speech pattern, or a deliberate pause for emphasis) rather than true disfluency. In practice, disfluency filters are typically tuned conservatively toward common, well-established patterns (clear filler words like "um"/"uh", exact self-correction patterns like "I mean, actually—") rather than aggressively removing anything that merely LOOKS unusual, accepting that some genuine disfluency will occasionally slip through translation rather than risk erasing meaningful content the filter misclassified as noise.

Check yourself — the answers, and why

Nine steps in, these are the calls you should be able to make cold. Pick one, then read why.

  1. Waiting for a complete utterance before translating is disqualifying for real conversation because…

    The whole pipeline needs to stream, producing partial output as input arrives, to keep pace with real conversation.

  2. Incremental translation must sometimes revise its own output because…

    A partial translation is inherently provisional when the target structure depends on words that haven't arrived yet.

  3. Phrase-level chunking (rather than word-by-word or full-sentence) exists to…

    It's a deliberate middle ground on the fundamental latency/quality trade-off in streaming translation.

  4. Voice-preserving synthesis matters because…

    Fidelity in translation extends beyond literal words to how something was actually delivered.

  5. A per-stage latency budget (rather than one aggregate target) is useful because…

    Splitting the budget gives each component a concrete, ownable target rather than one opaque combined goal.

How you’d open this design in an interview

Before any boxes: agree what it must do, pin the qualities that shape everything, then build — naming each trade-off as you make it. The walkthrough above is that exact order.

What it must do

Agree on these before drawing a single box.

  • Stream, don’t batch: produce partial output as speech arrives, not after the whole utterance finishes.
  • Transcribe live: streaming ASR emits continuously-updating partial transcripts, revising earlier words.
  • Translate incrementally: incremental MT that revises its own output as later words change the best translation.
  • Speak in the speaker’s voice: voice-preserving streaming TTS that carries prosody and delivery, not a generic narrator.
  • Budget the latency: split an explicit end-to-end budget across ASR, translation, and synthesis.

The qualities that shape everything

Each one names the mechanism that buys it.

Keep conversational pace
Stream the whole pipeline — each stage works on partial input concurrently — rather than batch-processing complete utterances, which stacks additive latency.
Feed downstream immediately
Streaming ASR emits partial transcripts continuously as audio arrives, revising earlier words if later context changes the interpretation.
Correct translation despite word order
A Re-translation Stabilizer re-evaluates and revises a segment as more of the sentence arrives, since a language’s grammar can withhold key structure until late.
Enough context without waiting for a sentence
Chunk at natural phrase or clause boundaries — a tuned middle ground between word-by-word (too little context) and full-sentence (too much latency).
Preserve speaker identity and delivery
Voice-preserving synthesis carries pitch, tone, and prosody so the translation sounds like that specific person, not a generic narrator.
No stage silently eats the whole delay
An explicit per-stage latency budget across ASR, translation, and synthesis, each component engineered and monitored against its own slice.

The trade-offs you say out loud

Senior signal isn’t the boxes — it’s naming what you gave up and why it was the right price.

Stream the whole pipeline over record-then-translate-then-speak

Waiting for a complete utterance stacks its full length plus every stage’s processing before the listener hears anything — multi-second dead air that makes conversation feel like a walkie-talkie.

Continuously-updating partial transcripts over waiting for a natural pause

Natural speech has few clean pauses within a longer thought, so waiting reintroduces meaningful latency; partial output that can be revised beats silence until certainty.

A re-translation stabilizer over locking in the first translation

With different word order (a German sentence-final verb), an early guess can be simply wrong, so treating partial output as provisional and revising it beats either high latency or a locked-in wrong answer.

Phrase/clause-boundary chunking over translating every word individually

Single-word chunks give almost no context and constantly revise; phrase-level chunks give enough local context for a confident translation while still emitting before the sentence completes.

Voice-preserving synthesis over one fixed generic voice

A generic voice strips speaker identity and emotional delivery — real information on a video call or live talk; voice preservation treats delivery as part of what’s being translated.

The answer, out loud

What a strong answer to “Design Realtime Speech Translation” sounds like, first question to last trade-off. It is about 7 minutes of talking; the whiteboard and the interviewer fill the rest of the 45. Read it aloud once, then close the page and give it yourself.

  1. 0–3 min

    Say what real-time has to mean here

    Before I draw anything I want to be clear about the bar. Chaining speech recognition, translation and synthesis gives you something that works, but if each stage waits for the one before it to finish, the listener hears nothing until the whole utterance has been spoken and then processed three times over. That is a walkie-talkie: talk, wait, listen, wait. What I am designing for is a conversation, where the translation keeps pace closely enough that people can overlap and interrupt each other normally. So the requirement is not accuracy on its own — it is useful output while the speaker is still talking.

  2. 3–8 min

    Stream every stage, not just the first

    The skeleton is a speaker, streaming recognition, incremental translation, streaming synthesis, a listener. The first thing I would say out loud is that all of them have to stream. If any single stage waits for a complete input, it reimposes the full delay on everything downstream, and the pipeline is only ever as live as its most patient stage. So each one takes partial input and emits partial output, working concurrently instead of in strict sequence. That is the decision the rest of the design pays for, because partial output means output that can turn out to be wrong.

    Built in step 1: Record, then translate, then speak
  3. 8–13 min

    Transcribe continuously, and allow corrections

    Recognition goes first because it is the biggest single source of lag. Rather than waiting for a pause, I would have it emit partial transcripts continuously and revise earlier words when later audio changes the likeliest reading. Waiting for pauses sounds safer, but natural speech inside a longer thought barely offers any, so you end up waiting almost as long as before. The trade I am accepting is having something immediately that may be corrected, over silence until certainty — and everything downstream is built to tolerate that correction rather than be surprised by it.

    Built in step 2: Partial transcripts, not one final answer
  4. 13–19 min

    Let the translation revise itself

    Now the genuinely hard part, and it is a language problem rather than an engineering one. Word order differs. In German the main verb often lands at the end of a clause, so a translation built from the first few words can need a fundamentally different structure once that verb finally arrives. I would not lock in the first guess, and I would not wait for the sentence to finish either. I would add a re-translation stabilizer that re-evaluates a segment as more of it arrives and revises the output when a meaningfully better translation becomes possible. Streaming translation is provisional by nature; the alternatives are high latency or a confident wrong answer.

    Built in step 3: Word order isn't the same across languages
  5. 19–24 min

    Pick the chunk the translator reads

    That raises how much text to hand the translator at a time. A word at a time gives it almost no context and revises constantly. A sentence at a time is the baseline problem again. So I would chunk on natural phrase or clause boundaries — enough local context for a confident translation without waiting for completeness — and I would treat the size as a tuned dial rather than a constant, because it trades translation quality against delay directly and there is no universally right setting.

    Built in step 4: Not word-by-word, not full-sentence
  6. 24–30 min

    Keep the person in the voice

    For synthesis I would use voice-preserving streaming text-to-speech rather than one fixed narrator. On a video call or a live talk, pitch, tone and delivery carry real information — urgency, warmth, a joke landing — and a generic voice throws all of it away, leaving the listener with a robotic intermediary instead of a specific person. Fidelity is not only about the words. The constraint is that this stage carries the same streaming, low-latency obligation as every other one, so whatever preserves the voice has to do it incrementally.

    Built in step 5: Should the translation sound like the speaker?
  7. 30–36 min

    Clean the input, then split the budget

    Two things make this survivable in production. Real speech is full of fillers, false starts and self-corrections, and translation assumes reasonably well-formed input, so I would put a disfluency filter between recognition and translation that keeps the corrected version and discards the abandoned false start. Then I would write the latency budget down stage by stage instead of as one aggregate figure. Each of the three gets its own few hundred milliseconds to defend, so when we miss the total it is obvious on sight which one spent it, and nobody gets to point at the stage next to them.

    Built in step 6: Real speech is messy
  8. 36–42 min

    The messy cases, and what I would do next

    The two real-world cases I would raise myself are a speaker switching languages mid-sentence, which needs per-segment language detection so that segment is re-routed through the right pair rather than forced through the wrong one, and telling a thinking pause from an actual ending, which wants silence, intonation and semantic completeness together rather than silence alone. The trade-off underneath all of it is the same one every time: earlier output means less context, which means more revision, and I have spent it deliberately at each stage. With more time I would take this past a single pair of languages — one speaker and a room of listeners who each want a different one, which forks the pipeline after recognition and turns the budget into a per-listener number rather than one path I can reason about end to end. And I would want an honest way to score the result, because accuracy measured on the finished text gives the same mark to a translation that arrived in time and one that arrived long after the moment had passed.

    Built in step 8: Code-switching and knowing when someone's actually done

What this teaches

Learn AI system design by building a live speech-to-speech translator like a conference interpreter earpiece or Google's live translation — streaming ASR, MT, and TTS chained under a tight latency budget, not the individual components in isolation. An interactive guide covering why record-then-translate feels like a walkie-talkie, streaming partial transcripts, why streaming translation must revise its own output as more context arrives (word order differs across languages), chunking policy, TTS voice/prosody preservation, disfluency filtering, per-stage latency budgeting, and code-switching.

Key takeaways

  • Waiting for a complete utterance stacks additive latency across every stage — real conversational pacing requires the whole pipeline to stream.
  • Streaming ASR emits continuously-updating partial transcripts rather than one final answer, unlocking everything downstream.
  • Incremental translation must be willing to REVISE its own output as more context arrives — word order genuinely differs across languages.
  • Chunking at phrase/clause boundaries balances context quality against latency, between word-by-word and full-sentence extremes.
  • Voice-preserving synthesis carries speaker identity and delivery through translation, not just literal words.
  • A disfluency filter cleans messy real speech before it reaches translation, which assumes reasonably well-formed input.
  • An explicit per-stage latency budget gives each component a concrete target, rather than one opaque aggregate goal.
  • Code-switching and end-of-utterance detection both need dedicated handling beyond a single fixed-language, silence-only assumption.

Concepts covered

  • A conversation, not a walkie-talkie
  • Record, then translate, then speak
  • Partial transcripts, not one final answer
  • Word order isn't the same across languages
  • Not word-by-word, not full-sentence
  • Should the translation sound like the speaker?
  • Real speech is messy
  • No single stage should eat the whole budget
  • Code-switching and knowing when someone's actually done
RUN IT YOURSELF

The words already spoken when "nicht" arrives

Step 3 says streaming translation must revise its own output; this file runs that revision for real. It streams five German sentences one word at a time through a prefix translator and releases the result under two policies — every hypothesis at once, and commit-only-after-K-agree — printing the words rewritten under the reader and the extra delay stability buys them with. Change COMMIT_K, hit Run, and watch the two numbers trade.

HOW TO READ THE CODE — 5 IDEAS
  1. German holds the participle and the negation until the end, so a later source word lands in the middle of the English and repaints every word after it — that is the stabilizer's whole job (step 3).
  2. Eager release rewrites 22 already-shown words. Commit-on-3-agreeing rewrites 7, and pays 1.55 source words of added delay for the difference.
  3. A commit never shrinks here, because audio cannot be un-said — which is why step 3 gives captions and the voice track two different release rules off one stabilizer.
  4. nicht arrives as word 10 of 11 and inverts the sentence. COMMIT_K=3 has already released 7 words and 5 are now wrong; rewrites reach zero only at K=8, where 2 of 11 words get out early. That floor is set by the grammar, not by the chunk dial (steps 4, 8).
  5. The clause with no auxiliary to hang not on is worse: English needs do-support, so I read the book becomes I do not read the book on the last source word — one German word repainting three English positions.
CPython · WebAssembly
built so a translation can revise itself mid-sentence without ever feeling like it stuttered — make the calls, mid-sentence code-switch, run the gauntlet.
Finished this one? 0 / 65 AI System Designs done

Explore the topic

See this alongside everything else on the same subject — handbooks, system designs, challenges and tools, in one place.

More AI System Designs