System Design Examples
The previous five pages built the BFF pattern one mechanism at a time — the projection layer, the architecture, the byte accounting, the fan-out latency math. This page assembles those mechanisms into three worked system designs: a streaming-media platform serving mobile, web, and TV from the same services; an authentication design that splits work between the gateway and each BFF; and a partner-API BFF that shows "client experience" is not only about devices. Each design ends the way a design review would — with the first bottleneck, the alternative that was rejected, and the trade-off that was knowingly paid.
The why-chain that runs through all three: clients diverge (screen, link, interaction model) → a shared response shape becomes the union of every client's needs → the union punishes the weakest link (cellular, a TV's D-pad, a partner's batch job) → so the projection must move server-side → and the team that owns the screen must own the projection → one BFF per client experience. Every latency and byte figure below is illustrative but internally consistent, built on the chapter's house numbers: ~120 ms cellular RTT, ~40 ms broadband WAN RTT, ~1–2 ms intra-DC network per hop (service work on top).
Design 1 — a streaming platform: three clients, four services, three payloads
The product is a video-streaming home screen. Four shared, screen-agnostic services own the data:
- catalog-service — full title objects: a ~600-char synopsis, cast list, six artwork renditions, genres, ratings, availability windows — call it ~2.5 KB per title raw.
- continue-watching-service — user 8842's in-progress titles with playback positions (~3 KB).
- entitlement-service — what this account may stream, at what resolution (~1 KB). This is authorization data and it stays here — no BFF decides entitlements.
- recommendation-service — ranked title IDs with scores (~2 KB).
Three client teams each own a BFF against those same four services. The projections differ because the screens differ:
- Mobile — a thumb-scrolled list of small cards. Six fields per card (id, title, thumbnail URL, duration, progress %, maturity badge) ≈ 140 B per card with JSON keys. Cellular bytes are the scarce resource, so nothing else ships.
- Web — mid-size cards with hover metadata: title, a 200-char teaser, two artwork sizes, top-3 cast, badges ≈ 700 B per card. Broadband can afford richness that improves hover/preview UX.
- TV — rows of exactly 10 tiles navigated by remote-control D-pad. Each tile carries a 4K art URL plus a 1080p fallback, title, and focus metadata ≈ 300 B per tile — plus a prefetch-hint block: the next row's tiles and art URLs, so the app warms its image cache before the user presses down. Pagination is a row cursor, because D-pad navigation is row-by-row and predictable.
Request flow, per client
Mobile (cellular, ~120 ms RTT), GET /mobile/home for user 8842:
| Step | Where | What happens | Cost |
|---|---|---|---|
| 1 | cellular | App issues one call, GET /mobile/home | ~120 ms RTT (paid once) |
| 2 | in-DC | Mobile BFF fans out in parallel: catalog (18 ms), continue-watching (8 ms), entitlement (6 ms), recommendations (24 ms) | max = 24 ms, not the 56 ms sum |
| 3 | BFF CPU | Project 20 titles (2 rows) down to 6-field cards: 20 × ~140 B ≈ 2.8 KB + framing ≈ ~3 KB | ~2 ms |
| 4 | — | One response returns; absorbed upstream: 20 × 2.5 KB catalog + 3 + 1 + 2 KB ≈ ~56 KB → ~3 KB (~19×) | client-perceived ~146 ms (120 + 24 + 2) |
Web (broadband, ~40 ms WAN RTT), GET /web/home — same fan-out, different projection:
| Step | What differs from mobile | Cost |
|---|---|---|
| 1 | One call over broadband instead of cellular | ~40 ms RTT |
| 2 | Identical parallel fan-out to the same four services | max = 24 ms |
| 3 | Projects 30 titles (3 rows) to hover-rich cards: 30 × ~700 B ≈ 21 KB + framing ≈ ~22 KB returned, ~81 KB absorbed (30 × 2.5 + 6 KB) | ~2 ms |
| 4 | Client-perceived total | ~66 ms (40 + 24 + 2) |
TV (living-room broadband, ~40 ms WAN RTT), GET /tv/home:
| Step | What differs | Cost |
|---|---|---|
| 1 | One call; same fan-out | 40 + 24 ms |
| 2 | Projects 3 visible rows × 10 tiles = 30 tiles × ~300 B ≈ 9 KB, plus a ~1 KB prefetch manifest for row 4 | ~10 KB returned, ~81 KB absorbed |
| 3 | User D-pads past row 3 → app follows the row cursor, GET /tv/home/rows?cursor=r4 → one ~3 KB row page (already half-warm from the hint) | ~40 ms per additional row page |
| 4 | Client-perceived first paint | ~66 ms, and row scrolling never blocks on artwork |
The punchline in one table — the same four services, three different payloads, and no client team negotiated with another:
| Client | First response shape | Bytes returned | Bytes absorbed | Perceived latency |
|---|---|---|---|---|
| Mobile | 2 rows of 6-field cards | ~3 KB | ~56 KB | ~146 ms |
| Web | 3 rows of hover-rich cards | ~22 KB | ~81 KB | ~66 ms |
| TV | 3 rows × 10 tiles + 4K art + prefetch hints | ~10 KB | ~81 KB | ~66 ms |
Notice what the numbers say about where each BFF earns its keep: mobile's win is bytes (56 KB → 3 KB on a metered radio); TV's win is interaction-shaped data (prefetch hints and row cursors exist only because the input device is a D-pad); web's win is the single round trip and server-side join. A shared endpoint could serve any one of these well — never all three.
Design 2 — authentication: what the gateway does, what each BFF does
Authentication is the sharpest illustration of the gateway-vs-BFF dividing rule from the architecture page: identical for every client → gateway; exists because this client is different → that client's BFF.
At the shared gateway (once, for everyone): TLS termination and token verification — check the JWT's signature, expiry, and issuer (~0.3–0.5 ms warm, consistent with the API-gateway chapter's figures). The gateway answers exactly one question — is this a valid principal? — and forwards the identity downstream. It does not know what a session cookie is, and it does not decide what user 8842 may watch.
At each BFF (per client, because credential handling genuinely differs):
- Web BFF — the token-handler shape. A browser is a hostile place for bearer tokens: anything readable by JavaScript is readable by an XSS payload. So the web BFF gives the browser only an HttpOnly, Secure, SameSite session cookie. Server-side, the BFF keeps a session store mapping that cookie to the user's tokens, and on each request exchanges the session for a short-lived downstream service token it attaches to its fan-out calls. Tokens never enter the browser; the cost is server-side session state the web team must operate (and a session-store lookup on every request).
- Mobile BFF — bearer pass-through with scope checks. The app performed an authorization-code-with-PKCE login and holds its tokens in the platform's secure storage — a phone can keep secrets a browser cannot. The mobile BFF verifies the bearer JWT's scopes match the route (
mobile.home.read) and forwards it. No session store, no cookie machinery. - TV BFF — the device-code flow (RFC 8628). A TV has no keyboard worth typing a password on. The TV BFF drives the OAuth device authorization grant: it requests a
device_code+ shortuser_codefrom the authorization server, displays "go toexample.com/activateand enterWDJB-MJHT" on screen, and polls the token endpoint at the server-specified interval while the user approves on their phone. The polling loop, the "authorization pending" handling, and the paired-device session that results are pure TV-client concerns — they belong in the TV BFF and nowhere else.
The boundary rule, stated once: authN at the gateway; per-client session and credential shaping in the BFF; authZ stays in the services. When the TV app asks for a 4K stream, entitlement-service — not the TV BFF — decides whether account 8842's plan allows it. Put entitlement logic in a BFF and it silently diverges from the answer mobile gets tomorrow.
Design 3 — the partner BFF: a client experience without a screen
A set-top-box operator wants your catalog inside their guide. Their "client" is a nightly batch job and a burst of morning traffic — a client experience as distinct as mobile-vs-TV, just with no pixels. Pointing them at your internal services would freeze your internals into a public contract; a shared gateway would give them the same chatty, fine-grained shape your own apps outgrew. So they get a BFF:
- Coarse aggregates, not chatty reads.
GET /partner/v1/catalog/updates?since=2026-08-01&cursor=…returns a paginated delta of changed titles — one call replaces the thousands of per-title reads an internal client would make. - Versioned contract. The
/v1is load-bearing: your mobile team reshapes its private BFF contract the same afternoon it changes a screen, but a partner contract is public — additive changes are cheap, removals need a deprecation window measured in months. Coarse, stable, versioned is the entire point. - Client-credentials auth + per-partner rate limits. No user is present, so the partner authenticates with the OAuth client-credentials grant, and the BFF enforces a per-key quota (say, tens of requests/second — illustrative) so one partner's backfill cannot starve the catalog service that your own home screens depend on.
The lesson: "one BFF per client experience" counts experiences by divergence of needs, not by device type. A partner API, an internal admin tool, and a voice assistant are all candidate experiences.
Deployment topology: independent curves, isolated failures
The topology is the diagram above: one shared gateway (TLS, authN, WAF, rate-limiting — cross-cutting, platform-owned) routes by path prefix to four BFFs, each owned and deployed by its client team, all fanning out to the same shared services. Two properties fall out of that separation:
- Each BFF scales on its own curve. TV traffic peaks in the evening (roughly 19:00–23:00, when living rooms light up); mobile spikes for minutes at a time when a push-notification campaign lands; web is steadier through the workday; the partner BFF is batch-shaped. Autoscale them independently and you buy capacity for four small, differently-timed peaks instead of one summed worst case — and a mobile push spike never queues behind TV prime time.
- A bad deploy is contained to one client. Worked failure: at 19:30 — TV peak — the TV team ships a release with a null-dereference on titles missing 4K artwork. The canary at 5% of tv-bff traffic starts throwing 500s on exactly the rows containing those titles; the TV team's error-rate alarm fires within minutes; they roll back. During the whole incident, mobile and web dashboards stay flat, because the blast radius of a tv-bff deploy is tv-bff. Now run the counterfactual through a single shared aggregation edge: the same bug ships to every client at once, and the 19:30 incident is platform-wide. Per-client BFFs turn "the edge is down" into "one screen of one client is down."
The operational bill for these properties is exactly the one the chapter has been honest about: four deployables to build, secure, monitor, and page on — N BFFs = N of everything.
Hostile design review: bottleneck, rejected alternative, price paid
| Design | First bottleneck | Alternative we rejected | Trade-off we accepted |
|---|---|---|---|
| 1. Streaming multi-client | recommendation-service at evening peak: all three BFFs fan out to it, and at 24 ms it is already every home screen's slowest branch — its p99 governs all three clients at once. Fix: per-branch deadline with a popular-titles fallback, plus per-BFF caching so 3× fan-out does not become 3× recs load. | One GraphQL edge — real per-client shaping without three deployables, but query-cost control, resolver N+1 storms, and a shared schema team were a poor fit for three strong client teams shipping on different cadences. | Three codebases whose auth, retry, and mapping logic can drift; a shared library and cross-BFF contract tests are mandatory, not optional. |
| 2. Auth at the BFF | The web BFF's session-store lookup sits on every request; if it is a remote store, that is an extra in-DC round trip per page view. Fix: replicated store with a short-TTL local cache. | Tokens in browser localStorage — no session state to run, but readable by any injected script; one XSS becomes full account takeover. | Server-side session state to operate, back up, and fail over — the web team now runs a small stateful system. |
| 3. Partner BFF | Unbounded delta queries: a partner asking for "everything since 2019" walks the whole catalog. Fix: cursor pagination with a bounded page size and per-key rate limits from day one. | Exposing internal service APIs behind the gateway — zero new code, but it freezes internal shapes into a public contract and hands partners N chatty endpoints. | A versioned public contract that must be supported for years; every field added to /v1 is a promise. |
When NOT to do any of this
A shared gateway with a thin aggregation layer beats per-client BFFs when the clients do not genuinely diverge: if mobile, web, and TV all render essentially the same card, three BFFs are three copies of one service — pay for one edge, add field filtering (sparse fieldsets) for the small differences, and stop. The same is true organizationally: if one team owns all the clients, the BFF's core payoff — independent ownership and deploy cadence — evaporates, and N BFFs is just N times the on-call surface. And if shaping needs are many, dynamic, and unpredictable rather than three stable experiences, a GraphQL layer (client-defined shaping) fits better than N server-defined projections. The streaming design above earns its three BFFs only because the projections, the interaction models, and the owning teams all diverge; remove any one of those and the design should shrink.
Drill ladder
- Q: Why does the TV BFF return prefetch hints and a row cursor while the mobile BFF returns neither? A: The interaction model is the client difference being served. D-pad navigation is predictable — row by row — so the next row's art URLs can be warmed ahead of the keypress; mobile scrolling is fast and erratic, and cellular bytes are the scarce resource, so mobile ships minimal cards and nothing speculative.
- Q: The gateway already verified the JWT. Why does the web BFF still run a session store? A: Verification (authN) and credential shaping are different jobs. The gateway answers "is this a valid principal?" once for everyone. The web BFF answers "how does this client safely carry credentials?" — a browser cannot hold bearer tokens safely, so the BFF holds them server-side and gives the browser only an HttpOnly cookie. Mobile skips all of that because secure device storage exists.
- Q: At evening peak, recommendation-service p99 degrades from 24 ms to 200 ms. What happens to each client, and what is the fix? A: All three home screens degrade together, because each BFF's fan-out is governed by its slowest branch and recs is that branch in every one of them. Fix: a per-branch deadline (e.g. ~60 ms) with a cached popular-titles fallback — recommendations are deferrable, the home screen is not — plus per-BFF response caching so three BFFs do not triple the load on the struggling service.
- Q: The partner asks for one more field in
/partner/v1/catalog/updates; the mobile team added a similar field to their BFF yesterday in an afternoon. Why is the partner change slower? A: The mobile BFF's contract is private — one team owns both sides, so change is a same-day deploy. The partner contract is public and versioned: adding a field is cheap, but it becomes a commitment you cannot walk back without a months-long deprecation window, so it gets reviewed as a promise, not a patch.
Takeaways
- The same shared services legitimately produce three different payloads — ~3 KB thumb-cards, ~22 KB hover-cards, ~10 KB D-pad tiles with prefetch hints — because the projection serves the screen and the link, not the endpoint.
- Auth splits cleanly on the dividing rule: authN once at the gateway; session/credential shaping per client in the BFF (cookie token-handler for web, bearer + scopes for mobile, device-code flow for TV); authZ stays in the services.
- A partner API is a client experience: coarse aggregates, a versioned contract, client-credentials auth, and per-key rate limits — the BFF pattern with a batch job for a screen.
- Independent BFF deployables buy independent scaling curves and failure isolation — a bad TV deploy at prime time stays a TV incident — at the honest price of N services to run.
- When clients, teams, or shapes do not diverge, collapse the design: a shared gateway with field filtering, or GraphQL for dynamic shaping, beats paying for BFFs you do not need.
Authored for this guide as the worked-examples capstone of the BFF chapter. Sources: Phil Calçado, "The Back-end for Front-end Pattern (BFF)" (philcalcado.com, 2015); Sam Newman, "Backends For Frontends" (samnewman.io) and Building Microservices, 2nd ed. (O'Reilly, 2021); Microsoft Azure Architecture Center, "Backends for Frontends pattern"; RFC 8628 (OAuth 2.0 Device Authorization Grant) for the TV login flow. All latency and byte figures are illustrative worked values consistent with this chapter's house numbers (~120 ms cellular RTT, ~40 ms WAN RTT, ~1–2 ms intra-DC), not measurements of a specific system.
🤖 Don't fully get this? Learn it with Claude
Stuck on System Design Examples? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.
Build the mental picture, not memorization.
I just read a lesson on **System Design Examples** (System Design) and want to truly understand it. Explain System Design Examples from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
Socratic — adapts to where you're stuck.
Teach me **System Design Examples** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
Active recall exposes what you missed.
Quiz me on **System Design Examples** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
Intuition + hook + flashcards for long-term memory.
Help me remember **System Design Examples** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.