Vibe Engines
YouTube
AI System Design

Design a Text-to-Speech Service

Step 1 / 9

Learn AI system design by building a text-to-speech / voice synthesis service like ElevenLabs, Play.ht or a hosted TTS step by step.

The numbers to beatbatchwhole file (URL)streamingfirst-audio fastSSML / voicecontrol inputs

The whole design, in writing

Learn AI system design by building a text-to-speech / voice synthesis service like ElevenLabs, Play.ht or a hosted TTS step by step. An interactive guide covering the synthesis pipeline (text normalization, acoustic model, vocoder), batch vs streaming synthesis, first-audio latency for voice agents, voices and prosody control, caching common phrases + CDN, GPU scaling, voice cloning safety, and the unhappy paths.

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

What makes text-to-speech hard?

Turning text into a natural voice has two faces. Batch: narrate a whole article or audiobook — quality matters, latency doesn't. Streaming: a voice agent that must start speaking within ~200ms of deciding what to say, or the conversation feels broken. And "natural" hides real complexity: pronouncing "Dr.", "$5", and "2024" correctly, with human-like rhythm and emotion.

Text+ voice · SSML
New in this step: Text.

A text-to-speech (TTS) service synthesizes speech through a pipeline — text normalization → acoustic model → vocoder — served in batch or streaming mode. The hard parts: getting the front-end (pronunciation) right, hitting the first-audio latency budget for interactive agents, and controlling GPU cost with caching.

What the new pieces do

Textclient
The text to speak, a chosen voice, and optional prosody controls (SSML). May be a whole document (batch) or a live stream (agent).

Step 1 · "Natural" is the hard part

More than reading letters

It's tempting to think TTS is "map each letter to a sound." Why is producing a convincing voice actually much harder than that?

Text+ voice · SSML
The system as it stands at this step.

Why is natural TTS much harder than mapping letters to sounds?

  1. "$5 on 3/4" must become "five dollars on March fourth", homographs need context ("read" past vs present), and the voice needs natural rhythm, stress and emotion — not a flat letter-by-letter readout. TTS is a multi-stage problem: normalize → pronounce → make it sound human.

  2. File size is trivial. The difficulty is linguistic and acoustic: correctly interpreting text into spoken words and rendering natural, expressive prosody — a flat phoneme mapping sounds robotic and mispronounces real text.

  3. They don't — that's exactly the problem. Context decides pronunciation (homographs), and raw text is full of numbers, symbols and abbreviations that must be expanded to words first.

Natural speech is a multi-stage problem. You must normalize text ($5 → "five dollars", Dr. → "Doctor", 3/4 → "March fourth"), resolve pronunciation including homographs from context ("read" past vs present), and render human-like prosody — rhythm, stress, intonation, emotion. A flat letter-to-sound map is robotic and wrong. This is why TTS is a pipeline, not a lookup.

Why this piece earns its place

Architecturally the front-end is the one stage you can change without training anything, and that is what makes it the cheap half of the page. A mispronounced product name is a lexicon entry that ships this afternoon; the same complaint about the voice itself is a training run and a re-evaluation. Adding a locale is mostly front-end work for the same reason — ordinals, currency, dates and address formats are local convention — long before it is a second acoustic model. What surprises people is that normalization is not decidable from the text alone. 3/4 is March fourth to one caller and the fourth of March to another; Dr. is Doctor before a name and Drive after one; St is Saint or Street. The front-end cannot read that out of the string, so the ambiguity belongs in the request — locale, plus a hint about what kind of text this is — rather than in a global default that is quietly wrong for half your customers. The one time you can skip most of this is when you own the text upstream: if it comes from templates you generate, emit spelled-out words yourself and hand the service something already spoken.

Step 2 · The front door

A gateway for batch and streaming

Like ASR, TTS serves two modes with different SLAs. What does the entry point do when a synthesis request arrives?

Text+ voice · SSMLGatewayfront door
New in this step: Gateway.

A gateway accepts the text, chosen voice, and optional SSML/prosody controls, checks the cache, and routes by mode. Batch: synthesize the whole text and return an audio file (URL). Streaming: begin emitting audio as soon as the first chunk is ready and stream it out over a persistent connection — essential for voice agents where first-audio latency is the metric that matters. Both drive the same synthesis pipeline and GPU fleet.

Why this piece earns its place

The two modes differ in connection shape, not only in latency target, and that is what makes the gateway real work rather than a router. A batch request behaves like any request: it arrives, it is served, the socket closes, and capacity is requests per second. A streaming request holds a connection open for as long as the utterance lasts, so the gateway's actual capacity is concurrent open streams — and an autoscaler watching request rate will report comfortable headroom while every worker is pinned. Deploys stop being routine for the same reason: you cannot drain a node with live streams the way you drain a request server, so a rolling restart cuts people off mid-sentence unless the drain refuses new streams and waits out the old ones. Retries split along the same line. A batch caller is waiting on a URL, so a retry is safe once the request carries an idempotency key and the second attempt lands on the first result rather than on a second take. Second take is the exact phrase: expressive models sample as they generate, so the same inputs return the same audio only when a seed is pinned — pin one on anything you intend to store, cache or re-serve. A streaming call has no retry at all, because the caller has already played the first half; the honest recoveries are resume-from-offset or stop.

  • batchwhole file (URL)
  • streamingfirst-audio fast
  • SSML / voicecontrol inputs

What the new pieces do

Gatewaygateway
Accepts synthesis requests, checks the cache, and routes to the pipeline — streaming audio out or returning a file.

Step 3 · Speak before it's all written

Streaming synthesis

A voice agent has decided to say a two-sentence reply. Waiting to synthesize the entire thing before playing any of it adds seconds of dead air. How do you make it feel instant?

Text+ voice · SSMLGatewaystream · batch
The system as it stands at this step.

A voice agent must reply fast. How do you avoid seconds of silence before it speaks?

  1. Synthesizing everything first adds latency proportional to the reply length before any sound — dead air that breaks conversational flow. You want to start speaking as soon as the first chunk is ready.

  2. Speaking rate doesn't address the initial delay before audio starts. The fix is to generate and stream the first chunk immediately, continuing to synthesize the rest as it plays.

  3. Chunk the text (by clause/sentence), synthesize the first chunk fast, and start streaming audio while later chunks are still being generated. First-audio latency drops to the time for one small chunk, and playback stays ahead of generation, so the reply begins almost instantly and continues seamlessly.

Stream the synthesis. Chunk the text (by clause/sentence), synthesize the first chunk fast, and begin streaming its audio immediately while later chunks are generated in the background — playback stays just ahead of generation. First-audio latency collapses from "synthesize the whole reply" down to "synthesize one small chunk", so a voice agent starts speaking almost instantly and continues seamlessly.

Why this piece earns its place

Chunking looks like a text-splitting detail, and the awkward part is that the text is itself arriving as a stream. The model upstream is still writing the sentence you are trying to cut, so the chunker commits to a boundary before it has seen what follows — wait for the sentence to finish and you have put back the delay you just removed, cut at the first comma and you commit to a break the rest of the clause would have argued against. The boundary itself is a real choice, not a detail. Synthesize each chunk knowing nothing of the last and you can generate several at once, but the acoustic model starts a fresh pitch contour every time, so the back half of a sentence begins as though it were a new sentence and a listener hears the seam. Carry the previous chunk's state forward and it stays one utterance, at the price of serializing generation: chunk two cannot start until chunk one has finished. Make that trade deliberately. And the failure to design against is not a slow start, it is a stall — if generation falls behind the player you get a gap mid-word, which a listener hears as broken rather than slow, so track the lead in seconds of buffered audio and widen chunks before it runs out.

  • ~200msfirst-audio target
  • chunksynth incrementally
  • play aheadof generation

Step 4 · Inside the pipeline

Normalize → acoustic → vocoder

Concretely, how does normalized text become a waveform? What are the stages and why are they usually split?

Text Front-endnormalize · G2PAcoustic Modeltext → melVocodermel → waveform
New in this step: Text Front-end, Acoustic Model, Vocoder.

Three stages. (1) Text front-end: normalize (numbers/dates/abbreviations → words) and convert to phonemes (grapheme-to-phoneme), with prosody from SSML/context. (2) Acoustic model: turn the phonemes + target voice + prosody into a mel spectrogram (how it should sound over time). (3) Vocoder: convert the mel spectrogram into an actual waveform. Splitting acoustic model from vocoder lets each be optimized (and swapped) independently; some modern systems fuse them end-to-end.

Why this piece earns its place

The seam between the two back-end stages is a contract, and it is written in numbers that rarely reach the design doc: sample rate, hop length, number of mel bins, the frequency range and the scale. An acoustic model and a vocoder are interchangeable only inside one of those conventions. Pair two that disagree and nothing throws — you get audio, and it is metallic, or pitched wrong, or slightly too fast. So the mel specification is part of the model contract, pinned beside both checkpoints, and swapping the vocoder independently is true only within it. That seam is also the debugger. When a sample comes out bad you can dump the mel, render it with a known-good reference vocoder and listen: still wrong means the acoustic model, fine means the vocoder. Losing that single inspection point is what fusing the stages actually costs you — not quality, but the ability to say which half broke at three in the morning. And size both halves in seconds of audio rather than in requests or characters: the acoustic model is decoding mel frames and the vocoder is rendering them, so what a request costs is set by how long its result takes to say, and one narration can outweigh a long tail of notification clips on the same dashboard.

  • front-endtext → phonemes
  • acousticphonemes → mel
  • vocodermel → waveform

What the new pieces do

Text Front-endservice
Normalizes text (numbers, dates, abbreviations, currency) into spoken words and maps to phonemes — the top error source.
Acoustic Modelservice
Turns the normalized text/phonemes (with the target voice + prosody) into a mel spectrogram — how it should sound.
Vocoderservice
Converts the mel spectrogram into an actual audio waveform — the second stage that produces the sound you hear.

Step 5 · Voices & expression

Voices, cloning & prosody control

Users want a specific voice, sometimes their own, and control over how it sounds — emphasis, pauses, emotion, speed. How does the service offer that?

TextGatewayText Front-endAcoustic ModelVocoder
The system as it stands at this step. · swipe to pan the diagram

A user uploads a 10-second audio clip and asks to clone that voice. What should the service require before generating anything in it?

  1. Possessing a recording of someone's voice (a public speech, a voicemail, a video clip) is not consent to clone it — this is exactly the gap that enables impersonation and fraud.

  2. Consent has to be structural, not assumed: verify the person cloning a voice is authorized to, gate the resulting voice behind that check, and watermark its output so misuse is detectable downstream.

  3. A label after the damage is done doesn't prevent the fraudulent call or the fake endorsement from being generated and sent in the first place — the gate belongs before generation, not as a disclaimer on the result.

Support multiple voices as speaker embeddings/models the acoustic stage conditions on, plus voice cloning (derive a new voice embedding from a short sample). Give control via SSML or parameters — emphasis, pauses, rate, pitch, emotion/style — so callers shape the prosody. Because cloning is powerful and abusable, gate it behind consent and verification, and add audio watermarking so synthetic speech is detectable. Voice is both the product and a safety surface.

Why this piece earns its place

A voice is not a setting, it is a stored artifact with an owner, and owners change their minds. Consent gets withdrawn, a clone gets disputed, a licensed voice reaches the end of its term — and each of those is a revocation, which is far harder than a refusal. Deleting the embedding stops new synthesis and does nothing about audio already rendered in that voice, sitting in object storage and cached at the edge, which is the material anyone would actually play back. So the voice id has to travel with the output, in the cache key and in the stored file's metadata, or a takedown arrives and you cannot even enumerate what to purge. The same artifact view explains why upgrading the acoustic model is never merely an upgrade: the new one renders the same voice slightly differently, so a customer who published a narration and comes back to re-render one corrected sentence gets a sentence that does not match the chapter around it. Voices get pinned versions, and old versions stay servable longer than you would like.

  • voicesspeaker embeddings
  • cloningfrom a sample (gated)
  • SSMLprosody control

Step 6 · Don't re-synthesize the world

Caching & delivery

Enormous fractions of TTS traffic are identical — the same IVR prompts, notifications, and stock phrases synthesized millions of times in the same voice. Re-running the GPU for each is pure waste. And finished audio needs fast delivery. How?

GatewayPhrase CacheText Front-endAcoustic ModelVocoderAudio StoreCDN
New in this step: Phrase Cache, Audio Store, CDN. · swipe to pan the diagram

Cache synthesized audio keyed by (text + voice + params): a repeated phrase is served from the cache and never touches the GPU — a massive cost and latency win for the common, static content that dominates many workloads. Store generated audio in object storage and deliver via CDN (audio is immutable and often re-played, ideal edge content). The result is a URL; only genuinely novel text pays for synthesis.

Why this piece earns its place

One hit rate across the whole service is a number that describes nobody. Traffic splits into classes that behave in opposite directions: prompts and notifications repeat almost totally, while a voice agent's replies are nearly all unique, since the point of them is that they were composed for this conversation. Average the two and you get a healthy-looking figure that hides GPU load being set entirely by the agent traffic. Size the fleet on the miss rate of the class that misses, and report hit rate per class or not at all. The same split distorts latency: with a cache in front of a GPU the distribution is bimodal, so the median measures the cache and synthesis only shows up in the tail — an SLO written on p50 stays green through a real regression in the thing you built. Two smaller facts worth carrying. The cache is bounded in bytes, not entries, since audio is enormous next to the text that keys it: one long clip evicts many short ones, and a policy that counts entries will thrash. And key it on the front-end's output rather than the raw string — two callers who punctuate the same sentence differently, or spell a number out, want byte-identical audio and would otherwise miss each other, at the cost of normalizing before the lookup, which is nothing against a miss.

  • cachetext+voice+params
  • hitskip the GPU
  • CDNdeliver static audio

What the new pieces do

Phrase Cachecache
Caches synthesized audio keyed by (text + voice + params), so common/repeated phrases skip the GPU entirely.
Audio Storedata
Durable storage for generated audio; the result is a URL, and static audio is delivered via CDN.
CDNcache
Serves finished, immutable audio from a nearby edge for fast, cheap playback and re-listens.

Step 7 · Feed the fleet

GPU scaling

Novel synthesis is GPU-bound (the vocoder especially), demand varies, and streaming has a tight latency budget. How do you run the fleet economically without breaking real-time?

TextGatewayPhrase CacheText Front-endAcoustic ModelVocoderAudio StoreCDN
The system as it stands at this step. · swipe to pan the diagram

Same GPU-economics as other inference services: batch synthesis requests for throughput on the batch path, but keep streaming batches small so first-audio latency stays low. Autoscale on load, keep models warm (avoid cold starts loading multi-GB weights), and use faster/lighter models or vocoders for the streaming path where latency dominates. Combined with the cache (most traffic never reaches a GPU), this keeps cost in check while protecting the interactive SLA.

Why this piece earns its place

One pipeline does not have to mean one pool. What the two paths gain by sharing is the pipeline itself — the same models, the same code path, one deploy, so the modes cannot quietly drift apart — and none of that requires them to share a device. Sharing devices costs the interactive SLO: a large batch occupying a GPU adds queueing delay to whatever streaming request lands beside it, and that surfaces as first-audio jitter rather than as an error, so it survives every dashboard you have. Partition the fleet, keep one pipeline across both partitions, and treat the streaming pool's lower utilization as the price of the SLO rather than waste to reclaim. Autoscaling then has a timing problem worth naming: a GPU is not ready when the instance is, it is ready when the weights are resident, and how long that takes is a property of how you package and place them rather than of the traffic that caused it. Spikes will not wait for it, so autoscaling covers the daily curve and a warm pool covers the spike. Under real overload, queue depth is the wrong lever. A streaming request that waits has already failed, because its audio lands after the conversation moved on, so requests carry a deadline and the gateway refuses what it cannot meet. The batch path, having no deadline, is what belongs on preemptible capacity.

  • batchthroughput path
  • small batchstreaming latency
  • warm + autoscalecost

Step 8 · The sharp edges

Pronunciation, latency & abuse

TTS fails in fluent, confident ways: the front-end mispronounces text, homographs go wrong, streaming latency slips, and voice cloning invites impersonation and fraud.

TextGatewayPhrase CacheText Front-endAcoustic ModelVocoderAudio StoreCDN
The system as it stands at this step. · swipe to pan the diagram

Harden the front-end — robust normalization for numbers/dates/currency/units, a pronunciation lexicon for names and jargon, and context-aware homograph resolution — since these errors are fluent and confident, not crashes. Protect the latency budget with chunked streaming, low-latency models, and caching. Combat abuse: consent/verification for cloning, watermark synthetic audio, rate-limit, and moderate text (don't voice disallowed content). Fail gracefully — fall back to a simpler voice/model rather than silence.

Why this piece earns its place

The failures on the other steps announce themselves — a stall leaves a gap mid-word, a missed deadline comes back as a refusal, a cold pool shows up as queueing. A wrong word does not, so the signal for this step has to be manufactured rather than monitored. In practice that means a held-out set of deliberately hard text — names, units, addresses, currency, mixed-language strings — re-rendered on every model or lexicon change and compared against audio someone already approved, plus a route for a customer to report a single wrong word. That route is the part built too late. Overrides are also mostly per tenant rather than global: a company's product name, a hospital's drug list, a game's invented places. If the lexicon is an engineering backlog item then every customer waits on your release train for a word only they say, so make it data they configure, with a test attached to each entry. And treat watermarking as a system rather than a feature: a mark is worth exactly what the detector can still read after the audio has been re-encoded and resampled on its way to wherever it gets played, and that detector is a service you staff and run with no revenue against it.

You did it

You just designed a text-to-speech service.

TextGatewayPhrase CacheText Front-endAcoustic ModelVocoderAudio StoreCDN
The finished design, end to end. · swipe to pan the diagram

Everything you assembled, in order

  • Natural TTS is a pipeline, not a lookup: normalize text, resolve pronunciation, render human prosody.
  • A gateway serves batch (whole file) and streaming (first-audio-fast) over the same synthesis pipeline.
  • Streaming synthesizes in chunks and plays the first audio immediately — first-audio latency is the metric.
  • The pipeline is text front-end → acoustic model (mel) → vocoder (waveform), often split for optimization.
  • Voices are conditioning inputs; cloning is powerful and must be gated by consent + watermarking.
  • Cache synthesized audio by text+voice+params so repeated phrases skip the GPU; deliver via CDN.
  • Scale GPUs with batching (small for streaming) + autoscaling + warm weights; harden the front-end.

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 natural text-to-speech a multi-stage pipeline instead of a letter-to-sound mapping?

    Because producing a convincing human voice requires solving several distinct problems that a flat mapping ignores. First, raw text is full of things that aren't words: numbers, dates, currency, abbreviations, and symbols that must be normalized into their spoken form — "$5 on 3/4/2024" has to become "five dollars on March fourth, twenty twenty-four", and "Dr." becomes "Doctor" — which is a linguistic front-end task, not phonetics. Second, pronunciation is context-dependent: homographs like "read" or "lead" are pronounced differently depending on meaning, and names and jargon need a pronunciation lexicon, so you need grapheme-to-phoneme conversion informed by context. Third, even with the right words and phonemes, flat delivery sounds robotic; natural speech needs prosody — rhythm, stress, intonation, and emotion — which the acoustic model must render. So TTS is structured as a front-end (normalize text, resolve pronunciation, plan prosody) feeding a back-end (produce the actual sound), and most embarrassing failures come from the front-end getting the words wrong while the neural voice reads them fluently and confidently.

  2. For a voice agent, why is time-to-first-audio the metric, and how do you minimize it?

    In a conversation, the perceptible quality is how quickly the agent starts speaking after it decides what to say — a gap of even a second or two of silence before each reply makes the interaction feel broken and unnatural, regardless of how good the final audio is. So the metric that matters for interactive voice isn't total synthesis time but time-to-first-audio. You minimize it by streaming chunked synthesis rather than synthesizing the whole reply before playing any of it: you split the text into small units (clauses or sentences), synthesize just the first chunk as fast as possible, and begin streaming its audio out over a persistent connection immediately, continuing to generate the remaining chunks in the background while the first ones play. Because playback stays just ahead of generation, the initial delay collapses from "synthesize the entire utterance" down to "synthesize one small chunk", so the agent begins speaking almost instantly and continues seamlessly. This is typically paired with a lower-latency (often smaller or quantized) model on the streaming path, since latency dominates there and a small quality trade is acceptable.

  3. What are the three synthesis stages and why are they usually split?

    The classic TTS pipeline has a linguistic front-end followed by a two-part acoustic back-end. Stage one is the text front-end: it normalizes the text (expanding numbers, dates, currency, and abbreviations into spoken words) and converts the result into phonemes via grapheme-to-phoneme mapping, incorporating prosody cues from SSML or context. Stage two is the acoustic model: it takes those phonemes together with the target voice and prosody and produces a mel spectrogram, a time-frequency representation that encodes how the utterance should sound over time. Stage three is the vocoder: it converts that mel spectrogram into an actual audio waveform — the sound you hear. The split between acoustic model and vocoder exists because it lets each be developed, optimized, and swapped independently — you can improve the vocoder (often the quality and latency bottleneck) without retraining the acoustic model, for instance — and it cleanly localizes concerns: the front-end owns pronunciation correctness, the acoustic model owns expressiveness and voice identity, and the vocoder owns raw audio fidelity. Some modern systems fuse these into end-to-end neural models, but the staged mental model still maps well to where errors and costs actually live.

  4. Why is caching such a big lever in a TTS service?

    Because a large fraction of real TTS traffic is exactly repeated: interactive voice response prompts, notifications, standard announcements, and stock phrases get synthesized over and over in the same voice with the same parameters, and every one of those is deterministic — the same text, voice, and settings always produce the same audio. Re-running expensive GPU synthesis for identical content is pure waste. So you cache synthesized audio keyed by the tuple of (text, voice, parameters): a request that matches a cache entry is served the stored audio directly and never touches a GPU, which is a massive win in both cost and latency for the common, static content that dominates many workloads. Only genuinely novel utterances pay the synthesis cost. On top of caching, finished audio is stored in object storage and delivered through a CDN, since generated audio is immutable and frequently re-played, making it ideal edge-cacheable content. Between the phrase cache absorbing repeated synthesis and the CDN fanning out delivery, the GPU fleet only has to work on new, unique text — which is the single biggest cost control in the whole system.

  5. Why does voice cloning demand consent, verification, and watermarking?

    Because the same capability that makes cloning valuable — deriving a convincing voice from a short audio sample and then making it say anything — is also directly abusable for impersonation and fraud. Without controls, someone could clone a real person's voice from a few seconds of public audio and generate speech that person never said, enabling scams (fake calls from a "family member" or executive), disinformation, and reputational harm. So a responsible TTS service gates cloning behind consent and identity verification, ensuring the person whose voice is being cloned has authorized it, rather than letting anyone upload any sample. It also watermarks synthetic audio — embedding a detectable, robust signal — so that generated speech can be identified as machine-produced downstream, which supports detection of misuse and provenance claims. These are paired with text moderation (refusing to voice disallowed or harmful content) and rate limiting. The underlying reason all of this is non-negotiable is that voice is simultaneously the product and a safety surface: the more natural and clonable the voices become, the higher the stakes of misuse, so consent, verification, and watermarking are built in rather than bolted on.

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. Natural TTS is hard mainly because…

    It's a pipeline: numbers/dates/abbreviations → words, homographs from context, expressive prosody.

  2. For a voice agent, the key metric is…

    Synthesize the first chunk fast and stream it while generating the rest — no dead air.

  3. The synthesis pipeline is…

    Front-end owns pronunciation; acoustic owns expressiveness; vocoder owns audio fidelity.

  4. Caching is a huge lever because…

    Cache by text+voice+params; repeated IVR/notification phrases become cache hits.

  5. Voice cloning requires consent + watermarking because…

    Cloning any voice is powerful and abusable; consent, verification and watermarks are mandatory.

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.

  • Synthesize: turn text (with a chosen voice + SSML) into natural-sounding speech.
  • Stream or batch: return a whole audio file, or stream first-audio-fast for a live voice agent.
  • Normalize + pronounce: expand numbers/dates/abbreviations to words and resolve pronunciation.
  • Voices & control: offer multiple voices, voice cloning, and prosody control via SSML.
  • Cache + deliver: serve repeated phrases from cache and static audio via CDN.

The qualities that shape everything

Each one names the mechanism that buys it.

Serve two SLAs from one pipeline
A gateway routes batch (whole file/URL) and streaming (first-audio-fast over a persistent connection) onto the same synthesis pipeline and GPU fleet.
A voice agent starts speaking instantly
Stream chunked synthesis — synthesize the first clause/sentence fast and play it while later chunks generate, collapsing time-to-first-audio to one small chunk.
Say the right words, not the letters
A text front-end normalizes numbers/dates/abbreviations to spoken words and resolves pronunciation (incl. homographs from context) before the acoustic model — the top TTS error source.
Cloning can’t enable impersonation
Gate voice cloning behind consent/verification of the voice owner and watermark synthetic audio so misuse is detectable — voice is a safety surface, not just a product.
Repeated phrases never touch the GPU
Cache synthesized audio keyed by (text + voice + params) so common IVR/notification phrases are cache hits; deliver immutable audio via CDN.
Run GPUs economically under a latency budget
Batch synthesis for throughput but keep streaming batches small, autoscale on load, keep models warm, and lean on the cache so most traffic never reaches a GPU.

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.

A multi-stage pipeline (normalize → pronounce → prosody) over a flat letter-to-sound mapping

“$5 on 3/4” must become “five dollars on March fourth”, homographs need context, and the voice needs natural rhythm and emotion. A flat phoneme map is robotic and mispronounces real text — TTS is a pipeline, not a lookup.

Chunked streaming synthesis over synthesizing the whole reply before playing any of it

Synthesizing everything first adds latency proportional to reply length — dead air that breaks conversational flow. Synthesize the first chunk fast and stream it while the rest generates, so first-audio latency is one small chunk.

Split acoustic model and vocoder over one fused end-to-end model

Splitting text→mel from mel→waveform lets each stage be optimized and swapped independently and localizes concerns — the vocoder is often the latency/quality bottleneck you tune without retraining the acoustic model.

Verify consent before a clone is usable over treating possession of an audio sample as permission

Having a recording of someone’s voice — a speech, a voicemail, a clip — is not consent to clone it; that gap enables impersonation and fraud. Gate the voice behind verification and watermark its output rather than labeling after the damage.

Cache audio by text+voice+params over re-synthesizing every request on a GPU

Huge fractions of TTS traffic are identical — IVR prompts, notifications, stock phrases synthesized millions of times. Serving repeats from cache skips the GPU entirely; only genuinely novel text pays for synthesis.

The answer, out loud

What a strong answer to “Design a Text-to-Speech Service” 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

    Two jobs sharing one pipeline

    Before I draw anything I want to split the problem, because text-to-speech is really two products sharing a pipeline. One is batch: narrate an article or a whole audiobook, where quality is everything and nobody is waiting on it. The other is streaming: a voice agent that has to start speaking within about two hundred milliseconds of deciding what to say, or the conversation feels broken. Same models, opposite priorities. I'd design for both, and almost every hard call later comes out of that split — how I chunk, how I batch, how I scale GPUs. The other thing I'd pick at early is that "natural" is doing a lot of work in the requirement.

  2. 3–8 min

    Reading letters is not speaking

    So let me take natural first, because it's tempting to treat this as mapping letters to sounds. Raw text isn't words. Dollar-sign five has to come out as five dollars, three-slash-four as March fourth, Dr as Doctor. Pronunciation depends on context — read and lead change with the tense, and names and jargon need a lexicon behind them. And even with the right words, flat delivery sounds like a machine: you need prosody, the rhythm and stress and emotion. That's why I'd build a linguistic front-end feeding an acoustic back-end rather than one lookup. It also tells me where the embarrassing failures will come from — the front-end picks the wrong words and the neural voice reads them beautifully.

    Built in step 1: More than reading letters
  3. 8–13 min

    One front door, two SLAs

    The entry point is a gateway. It takes the text, the chosen voice and any SSML, checks the cache, and routes by mode. Batch synthesizes the whole thing and hands back a URL. Streaming starts emitting audio as soon as the first chunk is ready and holds the connection open. Both feed the same synthesis pipeline and the same GPU fleet, which is deliberate — I don't want two services quietly drifting apart. And I'd put the cache check right here rather than deeper in, because a hit should never touch a model at all, so the earliest place that knows the whole request is the right place to ask.

    Built in step 2: A gateway for batch and streaming
  4. 13–19 min

    Start speaking before it is written

    For the agent path, the number I'd hold myself to isn't total synthesis time, it's time-to-first-audio. If a two-sentence reply is synthesized whole before anything plays, the delay grows with the length of the reply, and that's dead air in the middle of a conversation. So I'd chunk the text by clause or sentence, synthesize the first chunk fast, and stream it out while the rest generates behind it. Playback stays just ahead of generation. First-audio latency stops being a function of reply length and becomes the cost of one small chunk.

    Built in step 3: Streaming synthesis
  5. 19–26 min

    Inside the synthesis pipeline

    Concretely, three stages. The text front-end normalizes and converts to phonemes, carrying prosody from SSML or from context. The acoustic model takes those phonemes plus the target voice and lays out a mel spectrogram — a plan for the sound, not the sound. The vocoder renders that plan into a waveform. The reason I'd keep the acoustic model and the vocoder separate rather than fusing them is that they optimize differently, and the vocoder is usually where the latency and the GPU spend sit, so I want to swap or shrink it without retraining anything upstream. End-to-end models exist and I'd consider one, but the staged version maps to where errors and costs actually live.

    Built in step 4: Normalize → acoustic → vocoder
  6. 26–32 min

    Voices, and the gate on cloning

    Voices are a conditioning input to the acoustic stage — speaker embeddings — and cloning just means deriving one from a short sample. SSML gives callers the prosody knobs: emphasis, pauses, rate, pitch, style. The part I'd be firm about is cloning. Holding a ten-second clip of someone's voice is not consent to clone it, and that gap is exactly where impersonation and fraud come from. So I'd verify the uploader owns the voice or is authorized before the clone is usable at all, and watermark the output so synthetic speech stays detectable downstream. A warning label added after the fact doesn't stop the fraudulent call from being generated and sent.

    Built in step 5: Voices, cloning & prosody control
  7. 32–39 min

    Most traffic should never reach a GPU

    Then cost, which here is mostly a caching story. A huge share of real traffic is identical — the same IVR prompts and notifications synthesized over and over in the same voice — and synthesis is deterministic, so I'd cache the audio keyed by text plus voice plus parameters. A repeat becomes a hit that skips the GPU entirely. Finished audio goes to object storage and out through a CDN, since it's immutable and often replayed. On the fleet itself: batch hard on the batch path for throughput, keep streaming batches small so first-audio stays low, autoscale on load, and keep weights warm so nothing cold-starts into a live request.

    Built in step 6: Caching & delivery
  8. 39–45 min

    How it fails, and what I would do next

    What I'd watch for is that this service fails fluently. Break the front-end and the voice still sounds perfect while saying the wrong words — no error, no latency spike, nothing to page on. So the guards sit in front of the words rather than around the audio: the hardening goes into the front-end, where the wrong words come from. Moderation happens on the text, before anything is voiced, and rate limits on top, since the abuse here is how much speech you can put in someone's voice, not load. The trade-off under all of it is quality against latency — chunk size, batch size, which model serves the streaming path — and the cache is the only lever that gives back both at once. With more time I'd go into barge-in — what happens to audio I have already generated and paid for when the caller talks over the agent — and into what verifying a voice's owner actually looks like, since consent is easy to say and hard to prove.

What this teaches

Learn AI system design by building a text-to-speech / voice synthesis service like ElevenLabs, Play.ht or a hosted TTS step by step. An interactive guide covering the synthesis pipeline (text normalization, acoustic model, vocoder), batch vs streaming synthesis, first-audio latency for voice agents, voices and prosody control, caching common phrases + CDN, GPU scaling, voice cloning safety, and the unhappy paths.

Key takeaways

  • Natural TTS is a pipeline, not a lookup: normalize text, resolve pronunciation, render human prosody.
  • A gateway serves batch (whole file) and streaming (first-audio-fast) over the same synthesis pipeline.
  • Streaming synthesizes in chunks and plays the first audio immediately — first-audio latency is the metric.
  • The pipeline is text front-end → acoustic model (mel) → vocoder (waveform), often split for optimization.
  • Voices are conditioning inputs; cloning is powerful and must be gated by consent + watermarking.
  • Cache synthesized audio by text+voice+params so repeated phrases skip the GPU; deliver via CDN.
  • Scale GPUs with batching (small for streaming) + autoscaling + warm weights; harden the front-end.

Concepts covered

  • What makes text-to-speech hard?
  • More than reading letters
  • A gateway for batch and streaming
  • Streaming synthesis
  • Normalize → acoustic → vocoder
  • Voices, cloning & prosody control
  • Caching & delivery
  • GPU scaling
  • Pronunciation, latency & abuse
RUN IT YOURSELF

The wrong words, read perfectly

The text front-end of this page, runnable: it expands twelve sentences twice — once resolving each token from the token alone, once from its neighbours and from what the request declared — and scores both against a reference reading. Watch the word error rate, watch the raised an error column stay at zero in both, and watch row 3, where the extra evidence makes the reading worse. Change DEFAULT_LOCALE to en-GB, hit Run, and see which customers go quietly wrong instead.

HOW TO READ THE CODE — 5 IDEAS
  1. The same four digits are a year, a quantity, a version and a phone number here — and the string carries none of the answer (step 1).
  2. Both passes run one rule set; only the evidence differs. Same twelve sentences, 22.8% word error rate against 9.9% (step 4).
  3. Neither pass raised, and that zero is the code’s rather than the corpus’s: every reading is total over its input, so no denominator and no digit string can throw. The acoustic model and vocoder then say the wrong words beautifully (step 8).
  4. Row 3 is the one context reads worse: from 1984 tickets hands the year rule its cue and the token is not a year. More evidence is not monotonically better, and this failure is as silent as the other kind.
  5. Two errors survive full context: SQL wants a per-tenant lexicon entry, 3/4 wants a locale on the request. Flip DEFAULT_LOCALE and the rate moves to 12.9%, never to zero (steps 1, 8).
CPython · WebAssembly
built to turn words into a voice before the sentence finishes — make the calls, break the front-end, 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