App Architectures: Eight Case Studies
How real apps are put together, read through the patterns of the course.
The premise
The course taught roughly eight patterns, one at a time. This lesson shows them recombined in apps you use daily, because the secret of app architecture is that there is no secret: every famous app is a remix of the same small pattern set, chosen and weighted differently.
| # | Pattern | Taught in |
|---|---|---|
| 1 | API client over HTTPS/JSON; server owns truth | How Apps Actually Work |
| 2 | Token auth: verify server-side, store securely, refresh | A Login Flow End to End |
| 3 | Push via FCM/APNs: notify-then-fetch, token hygiene | Push Notifications End to End |
| 4 | Local DB as single source of truth; UI reads local | Offline Data & Sync |
| 5 | Outbox writes: optimistic UI, idempotency, retry | Offline Data & Sync |
| 6 | Delta sync: cursors, tombstones, versioned writes | Offline Data & Sync |
| 7 | Real-time channel: socket/SSE as a low-latency delta feed | Real-Time & WebSockets |
| 8 | Payments & entitlements: the right rail per goods type; receipts verified server-side into one table | Payments in Apps |
Each case study below names its remix. Details of specific companies' stacks are drawn from public engineering write-ups and talks (the blogs are in the sources below); treat the specifics as illustrative, the shapes as the lesson.
1. WhatsApp: the everything-composite
The canonical "uses every pattern at once" app.
Phone A Server (relay) Phone B local SQLite = truth | local SQLite = truth | | | |-- outbox: encrypt, send ------>|-- store ciphertext ----------->| (socket if open, |<-- ack (one tick) ------------| cannot read it | push if closed) |<------------- delivered ack (two ticks) -----------------------| | | iOS: notification extension | | decrypts push ON-DEVICE
- Your chat history lives on your phone, in SQLite; the server keeps messages only until delivered. That is why backup/transfer is a whole feature: the local DB really is the single source of truth (pattern 4), unusually strongly.
- The tick icons are an outbox pattern rendered as UI (pattern 5): clock = queued locally, one tick = server ack, two ticks = delivered ack from the recipient's device. You have been watching idempotent retry semantics your whole life.
- Socket while open, push while closed (patterns 3 + 7), exactly the split from the real-time lesson.
- End-to-end encryption (the Signal protocol) means the relay moves ciphertext it cannot read; on iOS, a notification service extension decrypts incoming pushes on-device before display: the rich-push hook from the push lesson doing security work.
- Engineering folklore: WhatsApp famously served hundreds of millions of users with a tiny team on an Erlang relay core, the payoff of keeping the server a thin, stateless-ish relay and pushing state to the edges.
Remix: 1 + 2 + 3 + 4 (strong) + 5 (visible in UI) + 7. No 6 in the classic design (delivery, not sync); multi-device support forced them to add sync machinery later, a lesson in how features drag patterns in.
2. Uber / Deliveroo: a live dashboard over a dispatch engine
Driver phone ──GPS every ~4s──► Dispatch backend ──fan-out──► Rider phone
(match supply to (car moves on map)
demand; owns the
Payments: own processor TRIP STATE MACHINE:
(physical goods, ~3%) requested → matched → arriving → riding → done
- Two apps, one state machine. The trip's state lives server-side; driver and rider apps are both just views of it, receiving transitions over their sockets. Neither phone is ever the truth (pattern 1 at its purest).
- The moving car is pub/sub fan-out (pattern 7): the driver's position stream is published; whichever server holds the rider's socket delivers it. Position updates are also throttled and interpolated client-side; the smooth glide is animation between ~4-second samples.
- Physical goods economics (pattern 8's classification): rides and food use their own processor at ~3%, which is what makes the category viable in the stores at all.
- Push carries the doorbell moments ("your driver is arriving", "order delivered") with short TTLs: a stale arrival push is worse than none (the push lesson, TTL section).
- Offline is deliberately shallow: an ordering app without connectivity is correctly useless; they polish the error states instead (the sync ladder's rung 1, chosen on purpose).
Remix: 1 + 2 + 3 (TTL-heavy) + 7 (the centrepiece) + 8 (physical). Minimal 4/5/6, and that is a design decision, not a gap.
3. Spotify: offline inverted, payments dodged
- The catalogue breaks the sync ladder's assumption: you cannot hold "all the user's data" when the data is a hundred million tracks. So Spotify inverts: stream from CDN with aggressive prefetch (the next track is already buffered before this one ends; that is the gapless magic), plus selective rung-3 offline where downloads are DRM-encrypted files useless outside the app.
- Playlists are the actual sync problem (pattern 6): edited on any device and synced through the backend. The data is small and structured (adds, removals, reorders on a named list), which is what makes it tractable next to the unsyncable catalogue, and single-owner playlists dodge most conflicts structurally (the sync lesson's dodge).
- The subscription is the reader-app pattern (pattern 8): purchased on the web at web economics; the app just asks the entitlements service what this account may do. Premium is a server-side answer, not an app-side flag.
- Connect (play on your speaker, control from your phone) is pattern 7 wearing headphones: every device holding a socket to the backend, control messages routed between them.
Remix: 1 + 2 + 4 (cache-heavy, not truth-holding) + 6 (playlists) + 7 (Connect) + 8 (reader-app). The clever bit is refusing rung 3 for the catalogue and buying it back selectively.
4. Instagram: the feed pattern
GET /feed?cursor=... ──► ranked page of posts + next_cursor
images/video from CDN, prefetched as you approach them
tap ♥ ──► UI updates INSTANTLY (optimistic) ──► POST /like queued behind it
- Cursor pagination as infinite scroll: each page returns items plus an opaque cursor, pattern 6's mechanics reused for a different question: continuing through a ranked result set rather than discovering what changed since last time. Ranking happens server-side (the order is recomputed per request, which is why refresh reshuffles).
- Prefetch is the perceived performance: media for the next screenfuls is already downloading from the CDN while you look at this one. The app feels fast because it cheats time, not because requests are fast.
- The like button is optimistic UI (pattern 5 in miniature): the heart fills instantly from a local write; the POST follows behind with retry. If it ultimately fails, the heart quietly un-fills; you have seen this happen on bad connections.
- Uploads are an outbox: a post created on the subway shows "posting…" and completes later: queued, retried, idempotent.
- Media itself never touches the API servers: photos and video live on CDNs; the API deals in metadata and URLs (a pattern worth stealing for any media-heavy app).
Remix: 1 + 2 + 3 (re-engagement heavy) + 5 (likes, uploads) + 6 (the feed's cursor machinery). The lesson: "the feed" is not a new pattern; it is cursor pages plus prefetch plus optimism, styled.
5. A banking app (Revolut-shaped): the security lesson in a suit
- Deliberately at the bottom of the offline ladder: balances and transactions are cached for instant display (rung 2), but nothing writes offline. A queued money transfer replaying hours later is a horror story; some operations should require connectivity. Choosing rung 2 here is a safety feature, not a limitation: the sync ladder read backwards.
- The security checklist deployed in full: certificate pinning (with managed rotation), Play Integrity/App Attest on sensitive endpoints, biometrics authorising a Keystore-backed key that decrypts the stored credential (the fingerprint does not "log you in"; it unlocks the credential's key), blanked app-switcher snapshots, no sensitive data in push payloads (the "You have a new message" pattern), short session lifetimes with silent refresh.
- The real defences are server-side: transaction risk-scoring, per-device attestation checks, anomaly detection, step-up confirmation for unusual transfers. The app is the polished view; the fraud engine is the product (the security lesson's closing rule at maximum stakes).
- Real-time is modest and correct: a push on card transactions (doorbell), polling/SSE for balance refresh. No websocket needed; money does not move at chat speed.
Remix: 1 + 2 (hardened) + 3 (notify-only payloads) + 4 (read cache only) + the entire security lesson. Notable for the patterns it refuses: no 5, no offline writes, on purpose.
6. Notion / Google Docs: rung 5, the one you buy
You type "h" ──► operation { insert "h" at pos 41 } ──socket──► server
│ transform/merge against
│ everyone else's concurrent ops
your view <──────────── everyone's operations, converged ───────────┘
(offline: ops queue locally, replay-and-merge on reconnect)
- The document is a log of operations, not a file. Every keystroke becomes an op flowing over a websocket; the server (Google Docs popularised operational transforms; newer tools lean on CRDTs) transforms concurrent ops so every client converges on the same result. This is the sync lesson's rung 5 made visible.
- Presence (the colored cursors) is pub/sub (pattern 7): ephemeral position events fanned out to everyone in the document, with TTLs so crashed clients' ghosts fade.
- Offline editing works because ops are exactly what an outbox wants to hold (pattern 5): queue locally, replay on reconnect, and the transform machinery doubles as the conflict resolution. This is why the op-log design is chosen: it makes rungs 4, 5, and 7 share one mechanism.
- The reason the sync lesson said "buy, don't build" is this card: every hard pattern in the course running simultaneously, plus mathematics. Teams of specialists maintain these engines full-time.
Remix: 1 + 2 + 4 + 5 + 6 + 7 fused into a single op-log mechanism: the maximal remix, and the strongest argument for using an existing engine (the Yjs or Automerge libraries, or a managed platform like Liveblocks) if you ever need it.
7. McDonald's: where this course started
The app that answered "what type of app does a takeaway use" (back in Lesson 1), now readable in full:
- The business model is pattern 3: push campaigns ("20% off today only") and the loyalty flywheel are the reason the native app exists at all; the ordering could have been a website.
- Offers and menus are server-driven UI: the app renders what a config API returns. Prices, promos, and layouts change per country and per day with no app release (an antidote to the slow release pipeline from Lesson 2).
- Payments are the physical-goods carve-out (pattern 8): own processor plus Apple Pay/Google Pay as wallets at card rates. Store distribution with no store tax, the best-of-both-worlds noted back in Lesson 1.
- Location is the quiet workhorse: store-finder, geofenced offers, and "start cooking when the customer is close" logic: platform services (Lesson 2) doing revenue work.
- The order flow is a small server-owned state machine (placed → paid → preparing → ready) with polling or push per transition: Uber's architecture at a fraction of the intensity, because the "driver" is a kitchen that does not move.
Remix: 1 + 2 + 3 (the business model) + 8 (physical) + server-driven UI. Modest sync, no real-time sockets needed: a reminder that most successful apps use fewer patterns than engineers expect.
8. The Endpoint 51 reader: this site's own book, same patterns
The closing example, deliberately the simplest: the hypothetical companion app for a $69 web-sold complete book package.
App ──magic-link/token auth──► existing backend ──► entitlements: lifetime? │ │ │<── chapter content (JSON/HTML) ─────┘ purchase NEVER happens in-app: │ cached locally (rung 2) Stripe, on the website, ~3% └── reading position synced when online (reader-app pattern)
- Auth: the existing magic-link flow issuing a token into SecureStore (pattern 2, steps 7–8 only; no OAuth dance needed).
- Content: chapters fetched over the authenticated API and cached locally: rung 2, with "download for offline" as an optional rung-3 upgrade per chapter, Spotify-style selective offline in miniature (pattern 4).
- Entitlement: the same table the Stripe webhook already writes; the app just asks "am I paid?" (pattern 8, reader-app variant: no store billing, no 15–30%).
- Sync: one tiny piece of state worth syncing, reading position. One user's single value dodges most conflict, and the one wrinkle (a stale device overwriting newer progress) has a one-line policy: furthest position wins, the Kindle rule (the sync lesson's structural dodge, smallest possible case).
- No push, no sockets, no outbox. A book does not interrupt you. The absence of patterns is the architecture.
Remix: 1 + 2 + 4 (light) + 8 (reader-app). Four patterns, all of which already exist in the website's backend, which is the point.
The lesson underneath the lesson
| App | 1 API | 2 Auth | 3 Push | 4 Local DB | 5 Outbox | 6 Delta | 7 Real-time | 8 Pay |
|---|---|---|---|---|---|---|---|---|
| ● | ● | ● | ●● | ●● | ○ | ● | ○ | |
| Uber | ●● | ● | ● | ○ | ○ | ○ | ●● | ● |
| Spotify | ● | ● | ○ | ● | ○ | ● | ● | ●● |
| ● | ● | ●● | ○ | ● | ●● | ○ | ○ | |
| Banking | ● | ●● | ● | ● | – | ○ | ○ | ○ |
| Notion/Docs | ● | ● | ○ | ● | ● | ● | ●● | ○ |
| McDonald's | ● | ● | ●● | ○ | ○ | ○ | ○ | ● |
| E51 reader | ● | ● | – | ● | – | ○ | – | ● |
●● = the app's defining pattern · ● = present · ○ = light/partial · – = deliberately absent
Three closing observations. First, the eight columns explain a startling fraction of every row; what remains (a CDN, a fraud engine, transform mathematics, server-driven UI) is domain machinery bolted onto the same eight-shaped frame, not a ninth client pattern. Second, the double-dots differ: an app's identity is which one or two patterns it pushes to the extreme (WhatsApp its local truth, Uber its fan-out, Spotify its entitlements, banking its auth). Third, the dashes matter as much as the dots: banking refusing offline writes and the reader app refusing push are architecture decisions of exactly the same weight as the features. Reading an unfamiliar app now works the same way: ask which columns it fills, which it doubles down on, and which it deliberately leaves empty.
Where Docker and Kubernetes fit
One family of famous names never appeared in these eleven lessons: Docker, Kubernetes, CI/CD, "the cloud". They are not missing pieces of the client story. They live on the other side of the API boundary, in how the server half is packaged and operated, and all you need from this course is to know which question each one answers.
Docker is not another way to build a phone app. It packages the backend your app calls (the Python process, its dependencies, its runtime) into a container that runs identically on a laptop and on a server. Kubernetes sits a level above that again: infrastructure for operating many containers across many machines, restarting them when they crash and scaling them when traffic spikes. It is never an alternative to Swift or Flutter; it is one way of running the thing your app talks to, and most backends never need it.
USER'S DEVICE (this course) YOUR INFRASTRUCTURE (the next story)
Browser ──────┐
Swift app ────┤
Kotlin app ───┼── HTTPS / API ──► backend ──┬── database
Flutter/RN ───┘ │ ├── queue / cache
│ └── object storage
HOW IT IS RUN
│
Docker (packages it)
│
one server / managed platform
│
Kubernetes (when scale warrants)
| Question | What answers it |
|---|---|
| How is the client built? | Swift, Kotlin, Flutter, React Native (Lesson 5) |
| How do client and server talk? | HTTPS, JSON, tokens, push, sockets (this whole course) |
| How is the backend packaged? | Docker |
| How does a release reach the server? | CI/CD pipelines |
| How are many containers operated? | Kubernetes, once one server stops being enough |
Every app in this lesson runs its backend somewhere on that lower path, and none of it changes a single row of the matrix above: the client-side patterns are indifferent to whether the API lands on one rented server or a thousand-node cluster. That is the course's thesis one last time. The boundary is the API, the app is a client, and everything below the line belongs to the server side's story. The free production map places each layer; Python Developer: From Requests to Production builds the server half hands-on in its production chapters.
Check your understanding
- Pick any app on your phone: which of the eight patterns does it fill, and which one or two does it push to the extreme?
- Why is a banking app refusing offline writes (or the reader app refusing push) an architecture decision rather than a missing feature?
- Which familiar piece of WhatsApp UI is the outbox pattern rendered on screen, and what does each tick mean?
- A teammate suggests your new mobile app "should probably run in Docker". Which category has been mixed up, and where does Docker actually sit in the map?
Sources and further reading
- The public engineering blogs behind this lesson's shapes: Meta Engineering (WhatsApp, Instagram), Uber Engineering, Spotify Engineering
Build the server half
Eleven lessons, one recurring shape: the app is a client, and the API underneath is the part you can build today. Python Developer: From Requests to Production teaches exactly that: 30 chapters in Python, from your first request to a deployed, tested service. Two full chapters are free.
Reviewed 7 August 2026.