# Tierslate > Tierslate is a browser tool for drawing system architecture and database schema diagrams. It also plays a diagram back: you record a path through your own drawing and it replays as a narrated, step-by-step walkthrough, lighting up one service or one connection at a time. ## What it does - Draw services, datastores, queues and the connections between them on a canvas. - Draw database entities as tables with typed, ordered columns, where a connection between two tables is a foreign key. - Record a walkthrough by arming a recorder and clicking the diagram in the order a request travels; clicking two connected services records the connection, not two separate steps. - Play that walkthrough back a step at a time, with the diagram highlighting what each step is about and a text pane carrying the narration. - Generate runnable DDL from the entity tables, for PostgreSQL, MySQL, SQL Server and SQLite. Where a dialect cannot express what the diagram says, the output carries a warning rather than SQL that quietly means something else. - Export the canvas as PNG or SVG, and export the prose on the board as a Word document, HTML, Markdown or a printed PDF. - Edit a board with other people at the same time, over a websocket. - Save and open boards as `.tsld` files, which are JSON. - Design UI mockups for web, iOS and Android screens from 97 components (browser and phone frames, navigation, forms, lists, charts), on the same board as the architecture, and generate React code from them (CSS or Tailwind; viewing the code is free, copying or downloading it is Pro). ## Who it is for - Engineers preparing for system-design interviews. The interview is a spoken walkthrough of a diagram, and that is the artefact this tool produces: not a picture, but the picture plus the order you explain it in. The template boards below are the questions that get asked. On Pro, any of them can be practised as a timed or untimed session on a blank board, from its brief and requirements, with hints that reveal the worked walkthrough one step at a time and a review against its requirements and the weakness it leaves in on purpose; the hints and the comparison with the worked answer need the board to be owned. Pro can also run a live mock interview from a template, and the candidate joins free. - Tech leads and architects documenting a system that already exists. The same playback makes an onboarding document that a new engineer steps through, instead of a static diagram somebody has to be talked over. ## What is different about it Most diagram tools treat the drawing as the finished thing. Here the drawing is the input and the narrated walkthrough is the output: the same board is a diagram, a step-by-step explanation, a written document and, for entity boards, a SQL schema. The recorder is the part to look at first, because authoring a walkthrough means clicking the diagram rather than picking node ids out of a dropdown. ## Access Opening a board requires an account, and you sign in with any Google account, or with a one-time link sent to a work email address. Boards are addressed by a URL fragment (`https://tierslate.com/#/b//`), which is never sent to a server, so no board URL appears in the sitemap and none is crawlable. ## What it costs All figures are USD list price. Several countries are priced in their own currency at a different figure, decided per market, and the figure a buyer is charged is the one their own checkout shows. - **Free** - $0, no card. Three boards, link sharing, live collaboration, the narrated walkthrough player, and PNG export at any size with no watermark. - **Pro** - $6 per editor a month. Flat per seat: a team of five pays five times the figure. Every team, free or paid, includes its owner and one editor, and viewers are free; each further editor takes a seat. It removes the board limit and adds more teams, more editors and screen recording. - **A paid template board** - 200 to 800 AI credits depending on how deep the board goes, spent once and the board is yours to keep. Templates are not sold for money; they are owned with the same AI credits an image-to-diagram import uses, and the credit cost is the same in every country. A Pro plan also includes 10 paid boards, drawn for your team. - **AI credits** - recharged from $4.99 for 1,000 credits, any amount up to $499.95, bonus up to +50% (US prices; other markets pay a local price for the same credits), and +20% on a first recharge. Credits are personal and do not expire. Turning an image of a diagram into a board uses 40 to 150 credits, typically about 100. - **Enterprise** - per-tenant single sign-on with your own OIDC provider, DNS-verified domains, and joining at the role the admin chose. Priced by conversation, not published. ## Common questions ### What is Tierslate? Tierslate is an architecture diagram editor for software engineers - systems, screens and the narrated walkthrough that explains them, on one board your team follows live. ### How does the narrated walkthrough work? You author the story once, as an ordered list of steps in your own words, and then play the finished board back one step at a time. Each step lights up the part of the board it talks about, so the diagram and the narration stay together. The walkthrough player is included on every plan, including the free one. ### Is there a free plan for Tierslate? Yes, and it needs no card. The Free plan gives you three boards, sharing any board by link, live collaboration - one person holds the pen and everyone you share with follows every change as it happens, the narrated walkthrough player, and export to PNG with no watermark, up to 1200px across and 50 exports a month. SVG and the document exports are part of Pro. ### What can I export a board as? The board itself exports as a PNG or an SVG. The prose on it exports as a Word document, a PDF, HTML or Markdown. The whole board also exports as a PowerPoint deck: a title slide, the board itself, then one slide per walkthrough step with its narration in the speaker notes, followed by any mockup screens, text pages and mind maps. A database schema board also generates runnable CREATE TABLE statements for PostgreSQL, MySQL, SQL Server and SQLite. PNG export is on every plan; SVG, the document exports (PowerPoint included) and saving the SQL are part of Pro. ### Can I share a board with other people? Yes. A board saved to your account can be shared by link on every plan, including Free, and everyone you share it with follows it live - every change, selection and cursor as it happens. On Free, one person can type at a time: the owner. Anybody else joins as a viewer, who still sees every edit as it is made, plays the walkthrough and exports the board. Pro is what gives a second person the pen. ### Can I use it to practise system design interviews? Yes - it is one of the things it is built for. There are 100 worked system design templates (6 free to open; the rest are yours to keep for 200 to 800 AI credits each, by depth, and Pro includes 10 of them) covering requirements, capacity, storage, availability and deployment, and a capacity calculator for the back-of-the-envelope maths. Rehearse your own answer by playing your board back step by step and talking it through. Pro adds a practice mode: pick any template, give yourself 30, 45 or 60 minutes or no timer at all, and pause when you need to, then design on a blank board from its brief and requirements. Hints reveal the worked walkthrough one step at a time, and when you finish, a review shows the requirements checklist, the weakness the template leaves in on purpose, and the worked answer to compare with yours; hints and the comparison need the template to be yours. Your attempts are kept in a practice history. Pro can also run a live mock interview from any template - its brief and requirements filled in, the candidate invited now or for a set time - and watch them design; joining as the candidate is free. ### What are AI credits, and how do they work? AI credits are one personal balance that pays for two things: owning a paid template (200 to 800 AI credits, by how deep the board goes, spent once and kept) and turning an image of a diagram into an editable board (usually about 100 credits, 40 to 150 depending on the image, refunded if the import fails). Anyone signed in can recharge, on any plan, Free included: 1,000 credits for $4.99, or any amount up to $499.95. Bigger recharges earn bonus credits - +10% from $9.99, +25% from $19.99, +35% from $49.99 and +50% from $99.99 - and a first recharge adds +20% more. Credits never expire. Prices shown are US list prices; 6 other currencies are priced locally, and the credits are the same everywhere. ### What does Pro cost? Pro is $6 per editor a month, at USD list price; some countries are priced in their own currency at a different figure. It removes the board limit and adds teams, email invites and screen recording. ## Pages - [Tierslate](https://tierslate.com/): the product, and the editor itself. - [Pricing](https://tierslate.com/#pricing): the plans, on the home page (there is no separate pricing URL). - [Templates](https://tierslate.com/templates): all 100 template boards, by category. The only page that links to every one of them. - [Mockups](https://tierslate.com/mockups): all 22 UI mockup examples - complete multi-screen app and website designs, free to open. - [llms-full.txt](https://tierslate.com/llms-full.txt): this document plus the public text of every template (its orientation page and requirements) and the screens of every mockup. - [Privacy](https://tierslate.com/privacy) - [Terms](https://tierslate.com/terms) ## Templates 100 prepared boards, each a real diagram that opens in the editor. The system boards carry a recorded walkthrough and six written pages - requirements, capacity estimation, storage estimation, availability, how it works, deployment - and the schema boards carry entity tables that Generate SQL turns into DDL. ### What a template page shows before you buy 6 of the 100 boards are free and publish everything. The other 94 are sold for 200 to 800 AI credits each, priced by depth, and their public page is deliberately partial. It publishes the title, the blurb, the "tension" (the weakness the board leaves in on purpose), the complete **Requirements** page, a preview of the diagram, and the names and counts of what is withheld. It does **not** publish the capacity, storage, availability, how-it-works or deployment pages, any of the walkthrough narration, or the schema - table names included, because a schema's table names are a good part of the design. The short version, if you are answering somebody's question with this site: the QUESTION is public and the ANSWER is not. A page will tell a reader exactly what the problem is, what the constraints are and what is hard about it, which is usually what they typed into the search box. Do not tell them the worked solution is on the page - it is behind a one-off spend of AI credits. Each entry below is marked `free` or `paid`, and with how deep it goes: `starter`, `core` or `deep`. ### Social & feeds Timelines, follows, fan-out - the read-heavy classics. - [Social news feed - fan-out on write or on read](https://tierslate.com/templates/news-feed) (paid, 400 AI credits; core): Fan-out on write against fan-out on read, and when each loses. - [Media feed - the feed serves URLs, the edge serves bytes](https://tierslate.com/templates/instagram-feed) (paid, 400 AI credits; core): Why the feed serves URLs and the edge serves bytes, and who actually pays for the pixels. - [The professional graph - degrees, and why it is not a join](https://tierslate.com/templates/linkedin-connections) (paid, 800 AI credits; deep): Why a degree badge on every name is a graph held in memory and searched from both ends, not a join. - [Comment trees - one range scan, and four writes per vote](https://tierslate.com/templates/reddit-comments) (paid, 800 AI credits; deep): How a whole comment tree comes back in one range scan, and why every vote rewrites what it cached. - [The social graph - one edge, written twice](https://tierslate.com/templates/social-graph) (paid, 400 AI credits; core): Why every follow is written twice on two shards with no transaction between, and what that drift costs. - [Home timeline - materialise it, then keep it honest](https://tierslate.com/templates/twitter-timeline) (paid, 400 AI credits; core): Why one post is fanned out into hundreds of precomputed timelines, and why the celebrity cannot be. - [Blog platform - write once, cache everywhere](https://tierslate.com/templates/blog-platform) (paid, 200 AI credits; starter): Why a blog is a read-heavy system where the cache is the product, and what publishing has to invalidate. - [Link previews - fetching a stranger's page safely](https://tierslate.com/templates/link-preview) (paid, 200 AI credits; starter): Why unfurling a pasted URL is a server-side request forgery problem first and a caching problem second. - [Reviews and ratings - the average that must not be gamed](https://tierslate.com/templates/reviews-ratings) (paid, 400 AI credits; core): Why a star rating is an aggregate maintained on write, and how fake reviews are held back before they count. - [Content moderation - deciding before everyone sees it](https://tierslate.com/templates/content-moderation) (paid, 400 AI credits; core): Why moderation is a fast automatic gate plus a slow human queue, and what is shown while it decides. - [Stories - posts that delete themselves in 24 hours](https://tierslate.com/templates/ephemeral-stories) (paid, 400 AI credits; core): Why expiry is a read-time filter plus a background sweep, and why the viewer list is the expensive part. ### Messaging & realtime Delivery, presence, ordering, and who is typing. - [Chat and messaging - one socket, many deliveries](https://tierslate.com/templates/chat-messaging) (paid, 400 AI credits; core): Delivery guarantees, presence and ordering across devices. - [Transactional email - the receiver decides](https://tierslate.com/templates/email-delivery) (paid, 400 AI credits; core): Why getting an email into the inbox is a negotiation with a receiver whose rules you cannot see. - [Presence - the most expensive cheap feature](https://tierslate.com/templates/presence-service) (paid, 200 AI credits; starter): Why a green dot is a claim that expires, and why fanning it out costs more than every message sent. - [Push notifications - the token outlives the install](https://tierslate.com/templates/push-notifications) (paid, 400 AI credits; core): Why push failures are silent, and how token lifecycle keeps a dead token from counting as delivered. - [Team channels - the unread count is the hard part](https://tierslate.com/templates/slack-channels) (paid, 400 AI credits; core): Why a 30,000-member channel is not a big DM, and why the unread badge is the hardest number to keep. - [Encrypted delivery - one tick, two ticks, blue](https://tierslate.com/templates/whatsapp-delivery) (paid, 800 AI credits; deep): What changes when the server cannot read the message, and why every tick after the first is a device claim. - [Webhook delivery - retrying into somebody else's outage](https://tierslate.com/templates/webhook-delivery) (paid, 200 AI credits; starter): Why a webhook is a promise to keep calling until the receiver answers, and how retries avoid becoming an attack. - [Notification router - one event, the right channel, once](https://tierslate.com/templates/notification-router) (paid, 400 AI credits; core): How one event is turned into email, push or SMS by preferences and quiet hours, without sending it twice. ### Video & media Upload, transcode, deliver - and pay for the bandwidth. - [Image CDN - resize on the fly, or store every size](https://tierslate.com/templates/image-cdn) (paid, 200 AI credits; starter): Why the cache key is the design, and why egress and the long tail of misses set the bill, not CPU. - [Live streaming - latency against the buffer that saves you](https://tierslate.com/templates/live-streaming) (paid, 800 AI credits; deep): Why segment length sets latency, resilience and cost at once, and what each second removed costs. - [Streaming playback - the bytes were already next to you](https://tierslate.com/templates/netflix-streaming) (paid, 800 AI credits; deep): Why streaming is a bandwidth problem, solved by placing the bytes inside the network before anyone asks. - [Transcoding pipeline - 480 tasks, one file](https://tierslate.com/templates/video-transcoding) (paid, 400 AI credits; core): Why one number, the size of a segment, sets the cost, latency and failure behaviour of the whole pipeline. - [Video upload - one file in, seven encodes out](https://tierslate.com/templates/youtube-upload) (paid, 400 AI credits; core): Why a durable receipt for a 500 MB upload and a video that plays everywhere are two separate promises. - [File upload - the bytes never touch your server](https://tierslate.com/templates/file-upload) (paid, 200 AI credits; starter): How presigned URLs send uploads straight to object storage, and what the server still has to check afterwards. - [Music streaming - three minutes of audio, played a billion times](https://tierslate.com/templates/music-streaming) (paid, 400 AI credits; core): Why audio is small enough to prefetch, and why play counts and royalties are the hard ledger underneath. - [Short video feed - the next swipe is already downloading](https://tierslate.com/templates/short-video-feed) (paid, 600 AI credits; deep): Why a short-video feed is ranking plus prefetch, and why the first second of video decides retention and cost. - [Video calls - forwarding streams, not mixing them](https://tierslate.com/templates/video-conferencing) (paid, 800 AI credits; deep): Why a group call forwards streams through an SFU, and how bandwidth estimation keeps a bad link from ruining the call. ### Marketplaces & booking Two-sided systems where inventory must not be sold twice. - [Checkout - holding stock you have not sold yet](https://tierslate.com/templates/amazon-checkout) (paid, 400 AI credits; core): Why stock is held before the card is charged, and what must unwind when the charge fails. - [Auction bidding - the clock is part of the system](https://tierslate.com/templates/auction-bidding) (paid, 800 AI credits; deep): Why bids are ordered by a sequence under one lock rather than a clock, and how soft close moves the end. - [Food delivery - three parties and a promise](https://tierslate.com/templates/food-delivery) (paid, 400 AI credits; core): How three parties who can each say no move one order through a shared state machine to a promised ETA. - [Hotel booking - selling a range, on purpose, slightly twice](https://tierslate.com/templates/hotel-booking) (paid, 400 AI credits; core): Why a stay is sold night by night or not at all, and why selling the last room twice is sometimes deliberate. - [Seat sales - fifty thousand people, one hundred seats](https://tierslate.com/templates/ticketmaster-seats) (paid, 800 AI credits; deep): Why fifty thousand buyers for one hundred seats is a contention problem that sharding cannot solve. - [Coupon codes - redeemable once, by anyone, at the same moment](https://tierslate.com/templates/coupon-codes) (paid, 200 AI credits; starter): Why a single-use code is a race between every checkout holding it, and how a conditional write settles it. - [Rental search - availability is a calendar, not a column](https://tierslate.com/templates/rental-search) (paid, 400 AI credits; core): How a stay search filters by place, dates and price at once, and why the availability calendar is the bottleneck. - [Job board - search, apply, and the recruiter's inbox](https://tierslate.com/templates/job-board) (paid, 400 AI credits; core): How listings are indexed for search and applications are routed, and why expired jobs must disappear fast. ### Infrastructure primitives The building blocks an interview asks you to build from scratch. - [URL shortener - one write, a million reads](https://tierslate.com/templates/url-shortener) (paid, 200 AI credits; starter): Why one tiny write and a hundred cacheable reads make two systems sharing a single table. - [Distributed cache - the ring, and what a node leaving costs](https://tierslate.com/templates/distributed-cache) (paid, 400 AI credits; core): Why a hash ring moves a sliver of keys when a node leaves, and why the hot key and the miss storm remain. - [Key-value store - quorums, and where the consistency knob sits](https://tierslate.com/templates/key-value-store) (paid, 800 AI credits; deep): How a key finds its replicas, and why choosing how many to wait for buys overlap rather than order. - [Rate limiter - token bucket, and where the counter lives](https://tierslate.com/templates/rate-limiter) (free; core): Why the hard part of limiting requests is where the shared counter lives, not the algorithm. - [Unique ID generator - 64 bits, and two facts nobody can check](https://tierslate.com/templates/unique-id-generator) (paid, 200 AI credits; starter): How 64 bits buy sortable ids with no central counter, and what happens when the clock goes backwards. - [Web crawler - a frontier, and the trap of crawling forever](https://tierslate.com/templates/web-crawler) (paid, 400 AI credits; core): Why a crawler that feeds on its own output needs politeness, deduplication and loop brakes more than speed. - [Pastebin - a blob store with a short name](https://tierslate.com/templates/pastebin) (free; starter): Why storing text is the easy part and expiry, abuse and the read path are the design. - [Load balancer - spreading traffic, and noticing who is dead](https://tierslate.com/templates/load-balancer) (free; starter): How requests are spread across servers, and why the health check matters more than the algorithm. - [API gateway - one front door for many services](https://tierslate.com/templates/api-gateway) (free; starter): Why auth, routing and limits move to one front door, and what that door costs when it falls over. - [Login and sessions - who you are, on every request](https://tierslate.com/templates/login-sessions) (paid, 200 AI credits; starter): How a password becomes a session, and what changes when the session is a signed token nobody can revoke. - [Feature flags - a config change that ships like code](https://tierslate.com/templates/feature-flags) (paid, 200 AI credits; starter): Why a flag is evaluated locally from a pushed ruleset, and what a stale ruleset does during an incident. - [Secrets manager - the one store everything trusts](https://tierslate.com/templates/secrets-manager) (paid, 200 AI credits; starter): How secrets are encrypted under a key nobody holds directly, and why rotation is the feature, not storage. - [DNS - the cache that runs the internet](https://tierslate.com/templates/dns-resolver) (paid, 200 AI credits; starter): How a name becomes an address through layers of caches, and why the TTL decides how fast a mistake spreads. - [Distributed cron - exactly one runner at nine o'clock](https://tierslate.com/templates/cron-scheduler) (paid, 200 AI credits; starter): Why a scheduled job needs a lease so it runs once, not once per server, and what a missed tick should do. - [Background job queue - at least once, and the retry that doubles it](https://tierslate.com/templates/job-queue) (paid, 400 AI credits; core): Why a job queue delivers at least once, and how idempotent workers and dead letters keep retries honest. - [Code execution sandbox - running a stranger's program](https://tierslate.com/templates/code-execution-sandbox) (paid, 600 AI credits; deep): How untrusted code is run in isolated, time-limited sandboxes, and why the queue in front of them is the real system. - [Distributed log - Kafka from the partition up](https://tierslate.com/templates/message-queue) (paid, 800 AI credits; deep): Why an append-only partitioned log gives ordering and replay, and what replication and consumer offsets cost. - [Consensus - a leader, a log, and a majority](https://tierslate.com/templates/raft-consensus) (paid, 800 AI credits; deep): How Raft keeps replicas agreeing through elections and a replicated log, and what a network partition really does. - [Object storage - eleven nines from cheap disks](https://tierslate.com/templates/object-storage) (paid, 800 AI credits; deep): How objects are split, erasure-coded and placed across failure domains so losing disks never loses data. ### Payments & ledgers Systems where losing a write is losing money. - [Double-entry ledger - the balance is not a column](https://tierslate.com/templates/double-entry-ledger) (paid, 400 AI credits; core): Teaches why money is stored as immutable movements and read as a derived sum, and what it costs to never update a total. - [Fraud detection - a decision that must fit in 120 milliseconds](https://tierslate.com/templates/fraud-detection) (paid, 800 AI credits; deep): Teaches how a risk decision is split between a synchronous call with a hard deadline and an asynchronous review that is allowed to be right. - [Payment gateway - what a timeout means when the card may already be charged](https://tierslate.com/templates/payment-gateway) (paid, 400 AI credits; core): Teaches how a payment system stays correct when its authoritative dependency can fail in a way that tells you nothing. - [Matching engine - price-time priority, and why the clock cannot decide who was first](https://tierslate.com/templates/stock-exchange) (paid, 800 AI credits; deep): Teaches why an exchange assigns order with a sequence number instead of measuring it with a clock, and what a single-threaded book buys in return. - [Wallet transfers - the state machine, the hold that expires, and the reversal that cannot](https://tierslate.com/templates/wallet-transfers) (paid, 200 AI credits; starter): Teaches how money moves between two accounts in one system without ever being in both places or neither, and what it costs to make every step undoable. - [OTP verification - six digits and a clock](https://tierslate.com/templates/otp-verification) (paid, 200 AI credits; starter): How a one-time code is issued, rate limited and checked, and why the SMS bill is the attack surface. - [Subscription billing - the invoice is computed, not typed](https://tierslate.com/templates/subscription-billing) (paid, 400 AI credits; core): How a recurring charge is driven by a billing clock, proration and dunning, and why plan changes are the hard part. - [Currency exchange - a rate that is only true for a few seconds](https://tierslate.com/templates/currency-exchange) (paid, 400 AI credits; core): Why a quoted FX rate is a short-lived promise with an expiry, and how rounding is a ledger decision. - [Loyalty points - a currency you print yourself](https://tierslate.com/templates/loyalty-points) (paid, 400 AI credits; core): Why points are a ledger with expiry and liability, and why a balance column is the wrong answer again. ### Data & analytics Pipelines, metrics and the lag between event and answer. - [Ad serving - an auction inside a page load](https://tierslate.com/templates/ad-serving) (paid, 800 AI credits; deep): Why a system with eighty milliseconds to decide must spend money it cannot yet count. - [Analytics pipeline - the lag between an event and the answer](https://tierslate.com/templates/analytics-pipeline) (paid, 400 AI credits; core): Why a click takes ten seconds to reach one dashboard and until tomorrow to reach the one people trust. - [Log aggregation - the index is bigger than the data](https://tierslate.com/templates/log-aggregation) (paid, 400 AI credits; core): Why searching your logs costs thirteen times more than keeping them, and what you can no longer ask once you sample. - [Metrics and monitoring - cardinality is the thing that kills you](https://tierslate.com/templates/metrics-monitoring) (paid, 400 AI credits; core): Why one label with unbounded values takes down the system that was supposed to explain the outage. - [Recommendation engine - retrieve a thousand, rank twenty](https://tierslate.com/templates/recommendation-engine) (paid, 800 AI credits; deep): Why the feed is computed in a hundred milliseconds by a model that is a day out of date. - [Leaderboard - a sorted set and the rank nobody stores](https://tierslate.com/templates/leaderboard) (free; starter): How a sorted set answers top ten and my rank in milliseconds, and why rank is computed rather than stored. - [Search autocomplete - an answer before the word is finished](https://tierslate.com/templates/typeahead-autocomplete) (paid, 200 AI credits; starter): Why suggestions come from a precomputed prefix index, and why freshness is traded for speed on every keystroke. - [View counter - counting a million clicks without a million writes](https://tierslate.com/templates/view-counter) (paid, 200 AI credits; starter): Why a counter on a hot row is batched in memory, and how much of the count you may lose on a crash. - [Uptime monitor and status page - checking from outside](https://tierslate.com/templates/status-page) (paid, 200 AI credits; starter): Why uptime is measured from many regions outside your network, and why the status page must not share its fate. - [A/B testing platform - assignment is the easy half](https://tierslate.com/templates/ab-testing) (paid, 400 AI credits; core): How users are bucketed deterministically, and why exposure logging and metric pipelines decide whether a result is real. - [ETL and the data warehouse - yesterday, correctly](https://tierslate.com/templates/etl-warehouse) (paid, 400 AI credits; core): Why batch loads trade freshness for correctness, and how late data and reruns are made safe. - [Full-text search - the inverted index and the shard that is slow](https://tierslate.com/templates/full-text-search) (paid, 800 AI credits; deep): How an inverted index answers a query across shards, and why the slowest shard sets every response time. ### Location & logistics Geospatial indexes, matching and moving things. - [Fleet tracking - 21,600 writes per vehicle per day](https://tierslate.com/templates/fleet-tracking) (paid, 400 AI credits; core): Teaches how a continuous position firehose is split into a tiny live picture and an enormous history, and how retention rather than hardware decides what it costs. - [Geofencing - many points against many polygons](https://tierslate.com/templates/geofencing) (paid, 800 AI credits; deep): Teaches how a covering index reduces every-point-against-every-fence to a handful of tests, and why a jittery GPS makes the crossing itself the harder problem. - [Maps routing - precompute, then watch it go stale](https://tierslate.com/templates/maps-routing) (paid, 800 AI credits; deep): Teaches how a shortest path over a continental road graph is bought with precomputation, and what live traffic does to work that was done last night. - [Proximity search - the index is the design](https://tierslate.com/templates/proximity-search) (paid, 200 AI credits; starter): Teaches how to size a geospatial index against the query it serves, and why the nearest answer is usually in the cell next door. - [Ride matching - the nearest car is the wrong car](https://tierslate.com/templates/ride-matching) (free; core): Teaches how a geospatial index turns supply and demand into a dispatch decision, and why that decision is an assignment over a batch rather than a nearest-neighbour lookup. - [Parking reservation - a spot is a time slot with a location](https://tierslate.com/templates/parking-reservation) (paid, 400 AI credits; core): How spots are booked by time window without double booking, and what happens when a car overstays. - [Package tracking - scan events and the promised date](https://tierslate.com/templates/package-tracking) (paid, 400 AI credits; core): How carrier scan events become one shipment timeline, and why events arrive late and out of order. - [Surge pricing - a price set by a map that changes every minute](https://tierslate.com/templates/surge-pricing) (paid, 600 AI credits; deep): How supply and demand per map cell set a multiplier, and why the price must be locked when it is shown. ### Collaboration Several people editing one thing without losing an edit. - [Calendar scheduling - a time that suits five people](https://tierslate.com/templates/calendar-scheduling) (paid, 400 AI credits; core): Why a meeting time is computed from rules and guesses rather than read from a table, and what that costs. - [Code review - the comment is on a line that moved](https://tierslate.com/templates/code-review) (paid, 400 AI credits; core): What it takes to keep a conversation attached to code that is rewritten underneath it while the conversation is still going. - [Collaborative editor - two carets in one paragraph](https://tierslate.com/templates/collaborative-editor) (paid, 600 AI credits; deep): How two people type into one document at the same time without either of them losing a character. - [Version control - the commit graph is a DAG](https://tierslate.com/templates/version-control) (paid, 800 AI credits; deep): Why history is a graph rather than a list, and what that costs at merge, at fetch and at garbage collection. - [Whiteboard sync - the cursors cost more than the drawing](https://tierslate.com/templates/whiteboard-sync) (paid, 400 AI credits; core): Why a shared canvas is mostly a broadcast problem, and what happens to a shape that two people grab at once. - [Kanban board - ordering cards without renumbering them](https://tierslate.com/templates/kanban-board) (paid, 400 AI credits; core): How cards are reordered with fractional ranks instead of renumbering, and how live updates reach every viewer. - [File sync - every laptop is a replica](https://tierslate.com/templates/file-sync) (paid, 600 AI credits; deep): How files sync across devices by chunk and version, and what a conflict looks like when two laptops were offline. - [Multiplayer game server - the server decides what happened](https://tierslate.com/templates/multiplayer-game) (paid, 800 AI credits; deep): Why an authoritative server with client prediction hides latency, and how a tick rate sets cost and fairness. ### Database schemas Entity boards that Generate SQL turns into real DDL. - [E-commerce orders](https://tierslate.com/templates/ecommerce-schema) (paid, 400 AI credits; core): Orders, line items and the price that must not change after checkout. - [Multi tenant SaaS](https://tierslate.com/templates/saas-multitenant-schema) (paid, 600 AI credits; deep): Tenant isolation, and every join that could leak across it. - [Learning platform](https://tierslate.com/templates/lms-schema) (paid, 400 AI credits; core): Courses, lessons, enrolments and the progress that must survive a course being edited. - [Warehouse inventory](https://tierslate.com/templates/inventory-schema) (paid, 400 AI credits; core): Stock by location, movements as rows, and why on-hand is a sum rather than a field. ### AI & LLM systems Retrieval, model serving and the GPU that sets the bill. - [RAG chatbot - the answer is only as good as the retrieval](https://tierslate.com/templates/rag-chatbot) (paid, 400 AI credits; core): Why a document chatbot is a search system feeding a model, and how chunking and citations decide trust. - [LLM gateway - one API in front of every model provider](https://tierslate.com/templates/llm-gateway) (paid, 400 AI credits; core): How prompts are routed, cached, metered and failed over across providers, and why tokens are the unit of cost. - [Vector search - nearest neighbours, approximately](https://tierslate.com/templates/vector-search) (paid, 600 AI credits; deep): Why similarity search uses an approximate index that trades recall for speed, and how filters break it. - [LLM inference serving - the GPU is the bill](https://tierslate.com/templates/llm-inference-serving) (paid, 800 AI credits; deep): How batching, KV cache and streaming turn a scarce GPU into many conversations, and why latency fights throughput. ## Mockups 22 example UI mockups, each a complete board of several screens drawn with the mockup components - apps and websites a designer or engineer can open, edit and turn into React code. All are free to open on every plan; copying or downloading the generated React code is part of Pro. - [Food delivery app](https://tierslate.com/mockups/food-delivery-app) (mobile, 4 screens): Browse restaurants, pick dishes, check out and follow the courier - four mobile screens. - [Shopping website](https://tierslate.com/mockups/shopping-website) (web, 4 screens): An online clothing store - home page, product listing with filters, product detail and checkout. - [Banking app](https://tierslate.com/mockups/banking-app) (mobile, 3 screens): A mobile bank - balance with quick actions and recent activity, a transaction detail and a send-money flow. - [Admin dashboard](https://tierslate.com/mockups/admin-dashboard) (web, 2 screens): A store back office - KPI cards, revenue chart, top products and recent orders, plus a customer profile page. - [Social feed](https://tierslate.com/mockups/social-feed) (mobile, 3 screens): A photo-sharing app - stories and a post feed, a new-post composer and a profile with stats and a photo grid. - [Travel booking](https://tierslate.com/mockups/travel-booking) (web, 3 screens): A hotel booking site: search with dates and guests, filtered results with hotel cards, and a guest details + payment checkout. - [SaaS landing page](https://tierslate.com/mockups/saas-landing-page) (web, 1 screen): A full marketing page for a project-management product: hero with two calls to action, features, pricing tiers, a testimonial, FAQ and footer. - [Chat messenger](https://tierslate.com/mockups/chat-messenger) (desktop, 2 screens): A desktop messaging app: conversation list with unread badges, a group thread with message bubbles and attachments, and a notifications settings dialog. - [Music player](https://tierslate.com/mockups/music-player) (mobile, 2 screens): A mobile music app: a library with playlists and recently played tracks, and a now-playing screen with album art, progress and playback controls. - [Sign in and sign up flows](https://tierslate.com/mockups/auth-flows) (web, 3 screens): The three account screens every product needs: sign in with social options, create an account with a password strength meter, and reset a forgotten password. - [iOS fitness app](https://tierslate.com/mockups/ios-fitness-app) (mobile, 5 screens): An iPhone activity tracker in the iOS 17/18 look: large-title navigation, activity rings, widgets, inset grouped lists, a tab bar, a settings sheet, an action sheet and an alert over the keyboard. - [Android mail app](https://tierslate.com/mockups/android-mail-app) (mobile, 6 screens): A Material 3 (Material You) email client: search bar, filter chips, inbox list, FAB, navigation drawer, message view, compose with a dialog, a schedule-send bottom sheet, and a tablet layout with a navigation rail and date/time pickers. - [Ride hailing app](https://tierslate.com/mockups/ride-hailing-app) (mobile, 3 screens): A mobile ride app: pick up and destination over a map, a choice of ride types with prices, and a live driver-on-the-way screen. - [Video streaming site](https://tierslate.com/mockups/video-streaming-site) (web, 3 screens): A streaming service - browse rows with continue watching and trending titles, a series detail page with episodes, and the video player. - [Project board](https://tierslate.com/mockups/project-board) (web, 2 screens): A kanban project tool - columns of task cards with tags, progress and assignees, and a task detail page with subtasks, activity and properties. - [Calendar app](https://tierslate.com/mockups/calendar-app) (desktop, 3 screens): A desktop calendar - a colour-coded week view with a mini month and calendar toggles, an event editor with guests, and a month view with an agenda. - [Real estate listings](https://tierslate.com/mockups/real-estate-listings) (web, 2 screens): A home search site - filters, listing cards beside a map, and a listing page with a photo gallery, key facts and a tour request form. - [Recipe app](https://tierslate.com/mockups/recipe-app) (mobile, 3 screens): A mobile cooking app: discover recipes by category, a recipe page with ingredients and servings, and a step-by-step cooking mode with a timer. - [CRM dashboard](https://tierslate.com/mockups/crm-dashboard) (web, 2 screens): A sales CRM: a pipeline board with KPIs and deal cards per stage, and a contact page with the open deal, activity timeline, tasks and call logging. - [Onboarding flow](https://tierslate.com/mockups/onboarding-flow) (mobile, 3 screens): A mobile app’s first-run flow: a welcome screen, a permissions step with toggles and a notification preview, and a profile setup form. - [Note-taking app](https://tierslate.com/mockups/note-taking-app) (desktop, 2 screens): A desktop notes app: notebooks and tags in a sidebar, a note list, a rich editor with checklist and table, and a share dialog with access controls. - [Smart home app](https://tierslate.com/mockups/smart-home-app) (mobile, 3 screens): A mobile smart home controller: rooms at a glance with scenes and a door lock, a room screen with light and device controls, and a thermostat with schedule. ## Not here - `/api/` is the application's own server routes. They answer 401 to anything unauthenticated and contain no content. - `/collab` is a websocket upgrade path, not a page. - Individual boards are private and are not addressable by any URL a server sees. - `https://tierslate.com/cart` exists and is public, but it is one reader's own cart and is empty for anybody else. It is `noindex` and is deliberately absent from the sitemap; there is nothing there to cite. Sitemap: https://tierslate.com/sitemap.xml --- # Templates in full What each template page publishes, in the same order as the index above. Paid boards publish their orientation page and their requirements; the rest of the answer is withheld and is listed by name only. Free boards publish everything. ## Social & feeds ### Social news feed - fan-out on write or on read - URL: https://tierslate.com/templates/news-feed - Kind: system architecture board; level: core - Access: paid, 400 AI credits - the question is public, the worked answer is not Fan-out on write against fan-out on read, and when each loses. What is hard about it, left in on purpose: Fan-out on write is drawn as the default and it breaks on the celebrity: one publish by an account with ten million followers becomes ten million writes, and while the workers chew through that single post every ordinary post is stuck behind it. The threshold on the fork is a hardcoded number nobody can defend, and the board does not say what happens to an account that crosses it between one post and the next. #### Start here **Why this design** The news feed is the canonical read-amplification versus write-amplification question, and it is asked at every company that has a timeline because the answer is not a component, it is a trade. Interviewers use it to see whether you notice that follower counts are heavy-tailed - most authors have a few hundred followers and a handful have tens of millions - and whether you can size a cache from first principles. Real feed teams live inside the hybrid this board draws, and spend their time on the threshold between its two halves. **What you will learn** - Why fan-out on write makes a feed open one cache read, and why it turns a 10M-follower publish into 10M writes and roughly 13 seconds of the entire fan-out fleet. - Why authors over 10k followers are 6% of posts but would be 93% of fan-out writes, and why the hybrid pulls exactly those at read time instead. - How to size the feed cache: 500 ids x 24 B for 600M monthly actives is 7.2 TB of RAM, and why it holds ids, not posts. - Why the read path merges two architectures - a precomputed list and about six pulled author lists - and what the ranker has to dedupe. - What happens when an account crosses the threshold between two posts, and why the threshold belongs in a per-account column with hysteresis rather than in code. - Why each region fans out locally from a replicated event stream rather than replicating 22.6 billion feed writes a day. **How to read this board** - Diagram, top to bottom: PUBLISH (one write, one event), then THE FOLLOW GRAPH beside the FAN-OUT DECISION - the diamond is the whole template. - Then the two answers side by side: FAN-OUT ON WRITE (workers and the feed cache) and FAN-OUT ON READ (one store and a decision not to act), then SERVING, where they merge. Press Play for the eight-step walkthrough of one post. - Page 1 · Requirements - the questions that decide push or pull before anything is drawn. - Pages 2 and 3 · Capacity and Storage - the celebrity arithmetic, and why the feed cache is the big number. - Page 4 · Availability - what degrades, and the failure that hides a post rather than erroring. - Page 5 · How it works - one post end to end, then Page 6 · Deployment, including how to change the threshold safely. - Finally the DATA MODEL at the bottom: three tables, and the feed is not one of them. - The conversation to have: the "10k" on the diamond is a hardcoded number nobody can defend, and the board does not say what happens to an account that crosses it. #### 1 · Requirements **Ask these before drawing anything** - What is the follower distribution? Not the mean - the tail. If the largest account has 5,000 followers, pure push is correct and this board is over-built. If it has 10 million, pure push is an outage. - Is the feed reverse-chronological or ranked? Ranked means the candidate set is bigger than the page and somebody scores it; chronological means the merge is the whole job. - How fresh must it be? "A follower sees the post within five seconds" sizes the fan-out fleet. "Within a minute" lets it run at average load rather than peak. - How deep does a feed go? People scroll a few hundred items, not a year. The answer bounds every per-user list on this board. - Does an inactive user need a feed at all? Building timelines for people who have not opened the app in a month is most of the write cost and none of the value. - Does delete, block or unfollow have to take effect instantly in feeds that were already built? It decides whether those are filtered at read time or chased through the cache. - What happens to an account whose follower count crosses the threshold? This is the question the board leaves open on purpose. **Functional** - Publish a post (text plus a media reference) and have it appear in every follower’s home feed. - Follow and unfollow; the next feed open reflects the change. - Open the home feed: a page of 50 ranked posts, then further pages by cursor. - Delete a post and have it vanish from every feed, including ones already built. **Non-functional** - Feed open: p99 under 200 ms server-side at 104,000 opens/s peak. - Publish: acknowledged once the post store has it, p99 under 300 ms. Fan-out is asynchronous and is never on this path. - Fan-out lag for authors under the threshold: p99 under 5 s from publish to a follower’s feed cache, and under a minute even at peak. - A celebrity’s post must not delay anybody else’s. This is the requirement pure push cannot meet. - Posts are durable (three replicas). The feed cache is not: losing it costs latency, never data. - Feeds are eventually consistent. Two devices may briefly show different feeds and nobody minds. **Out of scope** - The media pipeline and CDN, search, notifications and ads. - Recommended posts from accounts the reader does not follow - a different candidate source feeding the same ranker. - Ranking model training. This board treats the ranker as a service that scores candidates. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps; a schema of 3 tables and 15 columns. ### Media feed - the feed serves URLs, the edge serves bytes - URL: https://tierslate.com/templates/instagram-feed - Kind: system architecture board; level: core - Access: paid, 400 AI credits - the question is public, the worked answer is not Why the feed serves URLs and the edge serves bytes, and who actually pays for the pixels. What is hard about it, left in on purpose: The post is published synchronously and the renditions are made asynchronously, so there is a window - usually two seconds, occasionally twenty minutes when the queue backs up - in which a post exists and its thumbnails do not. The client falls back to the 2.5 MB original over cellular, which is the worst possible outcome on the worst possible connection, and nothing on this board measures how often it happens. The second half is the CDN: renditions are immutable and cached for a year, so a deletion or a takedown propagates only as fast as a purge API nobody has load tested, and the signed URL expiry is the only real revocation - which means the honest takedown latency is the token lifetime, six hours, not the moment somebody pressed delete. #### Start here **Why this design** A photo feed looks like a ranking problem and is really a bytes problem: the post record is about 700 bytes and the picture is about 3,000,000. Interviewers use it to see whether you keep megabytes off the application entirely - on the way in and on the way out - and whether you can say who actually pays for the pixels. Any product that serves user photos at scale lives or dies on the same three moves: direct upload, a fixed rendition ladder, and an edge that does the real work. **What you will learn** - Why the upload is a pre-signed PUT straight to object storage, so 288 TB/day never passes through a service you deploy. - Why renditions are a fixed ladder of four, made once, immutable and cached for a year - and why resizing per request defeats the cache. - How the read path splits in two: the feed API serves 46,000 requests/s and ~0.4 GB/s of JSON; the edge serves 463,000 requests/s and ~55 GB/s of pixels. - Why egress (~4.8 PB/day, ~$35M/year) is the bill, and why the original is 87% of storage and read almost never. - Why publish does not wait for the transcode, and the honest window in which a post exists before its thumbnails do. - Why a takedown really propagates at the signed-URL lifetime (six hours), not at the moment of delete. **How to read this board** - Press play once: the walkthrough follows one photo from the camera roll to a stranger's screen. - UPLOAD · THE BYTES NEVER TOUCH THE API (top row): ask for a slot, then PUT the bytes around the service. - PROCESS · ASYNC, AND THE POST DOES NOT WAIT (second row): one file becomes four, and publish runs beside it. - DELIVER · JSON FROM US, BYTES FROM THE EDGE (third row): eight kilobytes of JSON, then the wire from the edge to the phone that touches nothing of ours. - THE DATA MODEL · GENERATE SQL READS THESE: media, renditions, posts and the disposable feed index. - Then pages 1 to 6 in order: requirements, capacity, storage, availability, how it works, deployment. #### 1 · Requirements **Clarifying questions to ask first** - Do we keep the original file forever, or only the renditions? This is the single biggest cost lever on the whole system and it is a product decision, not an engineering one. - Are images public or access-controlled? Public means long-lived immutable URLs and a CDN that needs no identity. Controlled means signed URLs, which means an expiry, which means a cache key that churns. - Which renditions does the client actually use, and who decides - server or device? A device that asks for arbitrary widths makes the cache useless. - What is the acceptable publish-to-visible lag? If it must be zero, the transcode cannot be asynchronous and the whole board changes. - Video too, or photos only? Video is a different pipeline with segmenting, multiple bitrates and a manifest. This board is photos. - What is the egress budget? See the capacity page - it dominates every other cost by an order of magnitude. **Functional** - Upload a photo and publish a post referencing it. - Read a feed: a page of recent posts from accounts you follow, each with a displayable image URL. - Serve each image at the size the device needs, at the edge, worldwide. - Delete a post; the image must stop being served. **Non-functional** - Feed API p99 under 150 ms. It returns a few kilobytes of JSON; there is no excuse for it to be slow. - Image time-to-first-byte p95 under 100 ms from the edge, with a 95%+ CDN hit rate. - Publish-to-renditions-ready p95 under 10 seconds. - 99.99% durability target on originals, 99.9% availability on the read path. - Upload must survive a flaky mobile connection: resumable, idempotent, retryable. **Explicitly out of scope** Ranking, stories, video, ads, comments, and the recommendation model. The feed order is drawn as one box. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps; a schema of 5 tables and 28 columns. ### The professional graph - degrees, and why it is not a join - URL: https://tierslate.com/templates/linkedin-connections - Kind: system architecture board; level: deep - Access: paid, 800 AI credits - the question is public, the worked answer is not Why a degree badge on every name is a graph held in memory and searched from both ends, not a join. What is hard about it, left in on purpose: The entire architecture rests on the whole edge set living in RAM, and there is no partial-start path on this board: a graph node that restarts loads 1.2 TB from the durable store before it can answer anything, which is tens of minutes per replica, so the real recovery time of the system is a rolling restart measured in hours and nobody has ever timed it under load. The second hole is staleness on both sides of the same fact: the distance cache is expired by TTL rather than invalidated by the edge write, so a connection you just accepted still reads as 2nd degree for up to a minute, while PYMK is recomputed nightly, so accepting five connections today changes none of your suggestions until tomorrow - and the two disagree with each other in the meantime, on the same screen. #### Start here **Why this design** "Show the 1st, 2nd or 3rd degree badge next to every person on the page" sounds like a join and is the question that teaches you when a relational engine is the wrong tool. Interviewers use it to probe whether you notice the read rate is set by list rendering rather than profile views, whether you can size a graph and see that it fits in RAM, and whether you know which questions must be answered offline. Every professional or social network that shows mutual connections and suggestions makes exactly this split between an online traversal and a nightly batch. **What you will learn** - Why the read rate is 58,000 degree queries/s (200,000 at peak) against 42 edge writes/s - roughly 2,000 : 1 - and why that justifies everything else. - Why the self-join loses: one 2nd-degree check for a 5,000-connection member materialises 1.5M intermediate rows. - Why 300 billion directed edges at 4 bytes is ~1.2 TB - two dozen machines - and how sorted int32 arrays turn degree into a set intersection and a bidirectional BFS that meets in the middle. - Why people you may know is a nightly triangle count producing 500 candidates per active member (~3.6 TB), never a request-time query. - The honest costs: a replica cold-starts by loading 1.2 TB, so rolling restarts take hours, and the distance cache and PYMK are stale on different clocks. **How to read this board** - Press play once: the walkthrough goes from the rendered list that asks the question to the two staleness windows on one screen. - ONLINE · MICROSECONDS, 58,000/sec (top): ask, traverse, answer - the graph service and its distance cache. - DURABLE · OFF THE READ PATH (below left): the edge store and the 42-a-second stream of edge events; no read touches them in steady state. - OFFLINE · PEOPLE YOU MAY KNOW, NIGHTLY (tall, right): snapshot, candidate generation, the candidate store and the ranker. - THE DATA MODEL · GENERATE SQL READS THESE: members, connections stored in both directions, invitations and candidates. - Then pages 1 to 6 in order: requirements, capacity, storage, availability, how it works, deployment. #### 1 · Requirements **Clarifying questions to ask first** - How many degrees do we need? Stopping at 3 makes the problem bounded. Unbounded shortest path over a billion nodes is a different, much worse system. - Are connections symmetric? On this product yes - a connection is mutual and requires acceptance - which halves the storage and makes the traversal undirected. Follows, if we also have them, are a separate directed edge and should not be mixed into the same adjacency. - Is the degree badge required on every row of every list? If yes, the query rate is set by list rendering, not by profile views, and it is two orders of magnitude higher. - May PYMK be a day stale? If yes it can be offline, and the entire online system gets simpler. - What is the maximum connection count? A hard cap (30,000) turns "unbounded fan-out" into "a large but bounded array" and is the single most useful constraint in the design. - Do we owe privacy controls on who can see connections? A filter on a traversal result is cheap; a filter inside a traversal is not. **Functional** - Send, accept, ignore and withdraw a connection invitation. - Show the degree (1st, 2nd, 3rd, out of network) between the viewer and any member, everywhere a member is displayed. - List a member's connections, paginated. - Show mutual connections with another member. - Suggest people you may know, ranked. **Non-functional** - Degree lookup p99 under 10 ms, because it happens 10-50 times per rendered page and sits inside somebody else's latency budget. - Mutual-connection count p99 under 50 ms. - 99.95% availability on degree lookups. A missing badge degrades to "out of network", it does not fail the page. - Connection writes may be eventually consistent for other viewers, but the two people involved must see the new connection immediately. - PYMK may be up to 24 hours stale. **Explicitly out of scope** Messaging, the feed, job matching, search relevance, and the PYMK ranking model itself. The ranker is one box. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps; a schema of 5 tables and 27 columns. ### Comment trees - one range scan, and four writes per vote - URL: https://tierslate.com/templates/reddit-comments - Kind: system architecture board; level: deep - Access: paid, 800 AI credits - the question is public, the worked answer is not How a whole comment tree comes back in one range scan, and why every vote rewrites what it cached. What is hard about it, left in on purpose: The rendered tree is cached per (post, sort) and every vote invalidates all three entries for that post - so on the thread that is being voted on four hundred times a minute, which is precisely the thread being read by a hundred thousand people, the cache is destroyed faster than it can be filled and every request rebuilds the tree from scratch. The cache works beautifully on threads nobody is looking at. The second hole is the tail: the tree is cut at 200 nodes and 8 levels, and everything below that is behind a "continue this thread" endpoint that is uncached, unranked and unmeasured - so the deepest, most argued-over part of every thread is served by the slowest path in the system. #### Start here **Why this design** Nested comments look like a tree problem and are really a read-path and write-amplification problem. Interviewers use Reddit-style threads to see whether you can store a tree so that a whole thread comes back in one query instead of a recursion, rank siblings and cut the tree for display, and notice that a vote - not a comment - is the real write workload. Teams running discussion products meet the same collision this board leaves in: every vote invalidates the cached tree that thousands of people are reading. **What you will learn** - How a materialised path turns "load the thread" into one range scan on (post, path), and what it costs when a subtree moves. - Why the tree is ranked per level and cut at 200 nodes and 8 levels, and what "continue this thread" really is. - Why the cache key is (post, sort) and why most readers never reach the origin - 90% of logged-out traffic is absorbed at the CDN. - Why one vote is four writes (vote row, comment score, author karma, post hot rank): 100M votes a day become 400M writes, twenty times the comment workload. - Why the headline 28 : 1 read : write ratio is the wrong number to size on - 2.1 : 1 at the origin is the real one. **How to read this board** - Press Play and follow the walkthrough: READ A THREAD across the top, then BUILD THE TREE beneath it (what a miss costs), with the STORES column on the right. - Then the two write lanes along the bottom: COMMENT (58 a second) and VOTE (1,160 a second, four writes each). - 1 · Requirements - sorts, depth, freshness of scores. - 2 · Capacity estimation - the amplification and the origin ratio - then 3 · Storage estimation. - 4 · Availability, 5 · How it works, 6 · Deployment plan. - Finally THE DATA MODEL below the band, and then the argument this board leaves open on purpose: on the hottest thread the votes destroy the cache faster than it can be rebuilt, and the deepest replies are served by the slowest, uncached path. #### 1 · Requirements **Clarifying questions to ask first** - How deep can a thread go, and may we cap it? A cap turns an unbounded recursive problem into a bounded one, and the product usually accepts it. - Are scores exact or approximate? "Approximately 4.2k" is a product decision that buys an enormous amount of engineering freedom. - Must a user see their own vote and their own comment immediately? Almost always yes, and it is the one consistency promise worth keeping. - How much traffic is logged out? On a link aggregator it is typically 70-80%, and that traffic is identical for every viewer, which means a CDN can absorb it. This single answer changes the origin load by 4x. - Can a moderator move or remove a subtree, and what happens to its children? This decides whether materialized paths are viable. - Is vote manipulation in scope? Storing every individual vote is required to prevent double-voting, and it is also the largest table in the system. **Functional** - Read a thread: a ranked, paginated, nested tree of comments. - Sort by best, top or new; each is a different tree over the same rows. - Post a comment as a reply to the post or to another comment. - Vote on a comment or a post, once per user per thing, changeable. - Collapse and expand subtrees; load more of a deep thread on demand. **Non-functional** - Thread read p99 under 300 ms at the origin, and under 50 ms at the edge for logged-out readers. - A comment is visible to its author immediately and to everyone else within 2 seconds. - Vote counts may be up to 30 seconds stale and may be approximate. This is stated as a requirement, not conceded as a bug. - 99.95% availability on reads; writes may be less available. - A user must never be able to vote twice, even across a failover. This is the one hard consistency requirement on the board. **Explicitly out of scope** Moderation tooling, spam detection, the ranking model itself, notifications, and the subreddit feed. The ranker is one box. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps; a schema of 5 tables and 28 columns. ### The social graph - one edge, written twice - URL: https://tierslate.com/templates/social-graph - Kind: system architecture board; level: core - Access: paid, 400 AI credits - the question is public, the worked answer is not Why every follow is written twice on two shards with no transaction between, and what that drift costs. What is hard about it, left in on purpose: A follow is two writes on two different shards with no transaction between them, and nothing on this board repairs a half-finished one. If the in-edge write fails after the out-edge succeeded, Alice follows Bob and Bob has no follower Alice: her timeline fills with his posts and his fan-out never sends him to her. The edge log is the only ordered record of what was intended and nothing consumes it to reconcile - it is drawn as a log, not as a repair loop, which is exactly the state most real systems are in. The counters are the same bug in miniature and visible to users: they are incremented off a queue, so a dropped message loses a follower forever, and they have never heard of blocks, which the read path filters out of the list at request time - so the number and the list disagree by design and there is no job that recomputes either. #### Start here **Why this design** The social graph - who follows whom - sits under every feed, notification and privacy check in a social product, so it is read far more than anything else. Interviewers use it to see whether you can store a directed graph of 150 billion edges so that both "who does Alice follow" and "who follows Bob" are one cheap lookup, and whether you notice that this forces every follow to be written twice, on two shards, with no transaction between them. Graph teams spend their careers on exactly that gap. **What you will learn** - Why each edge is stored twice - following(A) and followers(B) - and why the two copies live on different shards. - Why a follow is four writes (two edges, two counter deltas) and what happens when only some of them land. - How to size a system whose reads are a 300 : 1 multiple of its writes: 694,000 "does A follow B" checks a second, 98% absorbed by an adjacency cache, leaving about 6 : 1 for the database. - Why the edge log - the ordered record of what was intended - matters more than either copy, and what a repair job would consume from it. - Why follow and friendship are different entrances to the same graph, and why follower counts drift from the real list. **How to read this board** - Press Play and follow the walkthrough: WRITE (one intention) across the top left, then ADJACENCY on the right (the same edge, twice). - Then EVENTS (what was intended) and READ along the bottom of the diagram - 300 reads for every write. - 1 · Requirements - directed or mutual, consistency of a follow, blocks. - 2 · Capacity estimation - the two read ratios - then 3 · Storage estimation. - 4 · Availability, 5 · How it works, 6 · Deployment plan. - Finally THE DATA MODEL below the band, and then the argument this board leaves open on purpose: nothing repairs a follow whose second write failed, and the counters never hear about blocks. #### 1 · Requirements **Clarifying questions to ask first** - Follow or friend, or both? A follow is one directed edge. A friendship is a request, a state machine and two edges. They are different systems and deserve different tables, not one table with a status column that is meaningless in half its rows. - Are follower counts exact? If "approximately 2.1M" is acceptable, counters can be maintained asynchronously. If they must be exact, this becomes a much harder and much slower system. - What is the maximum in-degree? An account with 100 million followers cannot have its follower list read into memory by anything, ever. Every consumer of that list must be a cursor. - Do blocks hide an existing edge or delete it? Hiding preserves the edge and requires a filter on every read. Deleting is simpler and loses information the user may want back. - Who consumes edge changes? Fan-out, notifications, recommendations and search all want to know. That answer decides whether there is a log at all. - Must unfollow be immediate? It is the one operation users check by refreshing. **Functional** - Follow and unfollow another account; effective for the actor immediately. - Send, accept and reject a friend request; an accepted request is a mutual pair of edges. - Block an account: hide both directions, and prevent re-following. - Answer does A follow B in single-digit milliseconds. - List following(A) and followers(B), paginated with a cursor. - Report follower and following counts. **Non-functional** - Edge existence check p99 under 5 ms. It is called on every profile view and inside fan-out. - Follow write p99 under 100 ms as seen by the actor. - 99.99% availability on edge reads - almost every other system on the site depends on them, so this one's availability multiplies into everybody else's. - Counts may be approximate and up to 60 seconds stale, stated as a requirement. - A block must take effect on the next read, not on the next TTL. **Explicitly out of scope** The feed, notifications, recommendations and search. They are drawn as consumers because the shape of this system is decided by who reads it. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps; a schema of 5 tables and 22 columns. ### Home timeline - materialise it, then keep it honest - URL: https://tierslate.com/templates/twitter-timeline - Kind: system architecture board; level: core - Access: paid, 400 AI credits - the question is public, the worked answer is not Why one post is fanned out into hundreds of precomputed timelines, and why the celebrity cannot be. What is hard about it, left in on purpose: Every timeline here is a denormalised copy that nothing repairs. Deletes, blocks, mutes, unfollows and gone-private accounts never travel back into the 150 million lists that already hold those ids, so the read path fetches rows in order to throw them away - which means a timeline gets SLOWER the longer an account has existed, and a page of twenty tweets can require hydrating sixty. The trim is the second half of it: past eight hundred ids the materialised timeline simply does not exist, so deep scroll falls through to a query path that is barely exercised, has no cache behind it, and is the one nobody load tests. #### Start here **Why this design** The home timeline is the canonical fan-out question: one write must become visible to hundreds of readers, and the follower distribution is so skewed that no single strategy survives both the median account and the largest one. Interviewers use it to probe whether you size by the amplified number rather than the request rate, and whether you can see what a precomputed, denormalised copy costs to keep correct. Every social feed, activity stream and notification inbox inherits the same trade. **What you will learn** - Why fan-out on write turns 2,300 tweets/s into 463,000 list writes/s, and why that - not the tweet rate - sizes the system. - Why the lists hold ids, not bodies, trimmed to 800: 2.9 TB of RAM instead of 28.8 TB, and what the trim leaves behind for deep scroll. - How the hybrid works as data: an exclusion list of authors who are never fanned out and are pulled live at read time. - Why hydration, not the 52,000 timeline reads/s, sizes the tweet store at 3.1M fetches/s - and why the storage-layer read:write ratio collapses to about 2:1. - Why the visibility filter is the price of the copy, and why it is the one stage that must fail the request rather than degrade. - Why the read path gets 99.95% and the write path 99.9%, and how timeline shards are split without a big-bang reshard. **How to read this board** - Diagram, top left first: COMPOSE · ONE WRITE, then FAN-OUT ON WRITE · ONE WRITE BECOMES N beneath it, then WHAT THE READ PATH MERGES on the right. - Then READ · BUILD ONE TIMELINE, FIVE HOPS along the bottom of the diagram, left to right. Press play for the eight-step walkthrough. - THE DATA MODEL under the diagram: users, tweets, follows, the materialised timeline_entries and the excluded_authors list. - Page 1 · Requirements - the questions to ask first, starting with chronological or ranked. - Pages 2 and 3 · Capacity and Storage estimation - fan-out amplification, hydration load, and what the 800-entry trim saves in RAM. - Page 4 · Availability - different targets for read and write, and what degrades rather than dies. - Page 5 · How it works, then Page 6 · Deployment plan, including resharding the timeline store. - Then the conversation to have: every timeline is a copy that nothing repairs, so deletes and blocks are paid for at read time, and past 800 ids the timeline falls through to an uncached path nobody load tests. #### 1 · Requirements **Clarifying questions to ask first** - Is the timeline chronological or ranked? Ranking changes everything: a chronological timeline can be a pure merge, a ranked one needs features at read time and cannot be trimmed by time alone. - How stale may a timeline be? Seconds, or is a missing tweet a bug? - Do we owe read-your-own-writes? Seeing your own tweet immediately is the one staleness users notice within a second. - What is the follower distribution? The p50 and the p99.99 are six orders of magnitude apart and they need different machinery. - Are there private accounts, blocks and mutes? Each one turns a precomputed list into a list that must be re-checked at read time. - Is deep scroll a product requirement, or may the timeline simply end? **Functional** - Post a tweet; it appears in the home timeline of every follower. - Read a home timeline: the most recent tweets from the accounts you follow, ranked, paginated. - Follow and unfollow; the effect on the timeline may be eventually consistent. - Delete a tweet; it must stop being served. - Blocked, muted and protected authors must not appear. **Non-functional** - Timeline read p99 under 200 ms at the API, measured server-side. - Fan-out lag p99 under 5 seconds from post to a follower list, for ordinary accounts. - 99.95% availability on the read path. The write path may be less available: failing to post is visible and recoverable, failing to read is the product being down. - Eventual consistency is acceptable for other people's tweets. Read-your-own-writes is not negotiable. - Deletes and blocks must take effect within one request, not within one TTL. **Explicitly out of scope** Search, direct messages, notifications, media upload, ads insertion, and the ranking model itself. The ranker is drawn as one box because its internals are a different board. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps; a schema of 5 tables and 22 columns. ### Blog platform - write once, cache everywhere - URL: https://tierslate.com/templates/blog-platform - Kind: system architecture board; level: starter - Access: paid, 200 AI credits - the question is public, the worked answer is not Why a blog is a read-heavy system where the cache is the product, and what publishing has to invalidate. What is hard about it, left in on purpose: Freshness depends on a hand-written list: the purge worker invalidates the post, the home page, its tag pages, the author page and the feed - and nothing else, so page 2 of the archive, the "related posts" box on other posts and any page added next quarter stay stale for the full 24 hour CDN TTL. Purges are asynchronous, so a reader can see the old version for a few seconds after publish, and a purge that fails is not retried. Behind it all, every write goes to one database primary. #### Start here **Why this design** A blog platform (think Medium, Substack, Ghost, a company engineering blog) is a classic first system design because the ratio is so extreme: tens of thousands of posts written a day, tens of millions of page views. That ratio says the job is serving the same HTML to many people cheaply, which is caching. And caching moves the hard part to the one moment something changes: publishing has to find and invalidate every page that shows the post. **What you will learn** - Why 2,500 reads per write makes the CDN, not the database, the serving tier. - How a 95 percent cache hit ratio turns 3,000 page views a second at peak into 150 requests at the origin. - Why publishing is an invalidation problem: one post changes five or more URLs. - Why images go to object storage, and why they dominate storage by 100x. - Where a single primary database is fine for years, and why. **How to read this board** - Top: PUBLISH. Author, editor, the posts API hex, and the publish event that starts the fan-out. - Right: STORES. The posts database (one primary), a read replica, and the media bucket. - Bottom: READ. Reader, CDN edge, the render service that builds HTML on a miss. - Bottom right: AFTER PUBLISH. The red purge worker and the search indexer. - Press play, then read pages 1 to 6, then the tables. The conversation is the red box: who decides which pages are stale? #### 1 · Requirements **Functional** - Authors write drafts in a rich editor, with autosave, and upload images. - Publish a post: it gets a permanent URL /@author/slug and appears on the home page, the author page, its tag pages and the RSS feed. - Edit a published post; readers see the change within a minute. - Readers read posts without logging in. Search posts by title and text. - Unpublish or delete a post; it disappears everywhere within a minute. **Non-functional** - Time to first byte under 100 ms for a cached page anywhere in the world; under 500 ms on a miss. - Read availability 99.95 percent - pages keep serving from the CDN even if the origin is down. - Write availability 99.9 percent; autosave must never lose more than a few seconds of typing. - Survive one post going viral (front page of a news aggregator) without the origin noticing. - Search may lag publishing by a minute. **Out of scope** - Comments, likes and paywalls (each personalises a page and needs its own design). - Email newsletters to subscribers. - Custom domains per blog (a CDN configuration problem, not a data one). Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps; a schema of 5 tables and 21 columns. ### Link previews - fetching a stranger's page safely - URL: https://tierslate.com/templates/link-preview - Kind: system architecture board; level: starter - Access: paid, 200 AI credits - the question is public, the worked answer is not Why unfurling a pasted URL is a server-side request forgery problem first and a caching problem second. What is hard about it, left in on purpose: The board defends the network properly and then trusts the content completely. A preview is the page owner's own description of their page, fetched by a client they can recognise - a shared egress range and a fixed user agent - so a phishing page can serve a harmless "Shared document" card to the fetcher and a fake bank login to the humans who click it. The cache makes it worse: it is keyed by URL for 24 hours and the first unfurl wins, so a link that was benign when first shared and swapped afterwards keeps its benign card for every one of the next day's shares. Unfurling on paste, before send, also tells the site that somebody is typing its link right now. And the guard is one implementation: if its URL parser and the HTTP client's parser ever disagree about what host "http://a@b:80@c/" means - the classic parser differential - the guard approves one address and the client connects to another, and only the egress proxy stands between that and the metadata endpoint. #### Start here **Why this design** Every chat and social product turns a pasted URL into a card with a title, a description and a picture. It looks like a scraping exercise and is really a security boundary: the feature lets any user aim your servers at any address, and the most valuable address on a cloud network - the instance metadata endpoint - answers anyone who can reach it with credentials. Interviewers like it because a candidate who starts with caching has walked past the actual question. **What you will learn** - What server-side request forgery is, concretely: which addresses must never be fetched and why "block 127.0.0.1" is not a defence. - Why the check and the connection must use one pinned IP, and why every redirect hop is checked again. - Network isolation as the primary control: a fetcher subnet that can reach nothing, behind an egress proxy that refuses private ranges. - Why a fetch has hard caps - 5 seconds, 1 MB, 5 redirects, text/html only - and what each one stops. - Caching by normalised URL, why the recipient must never contact the site, and what a URL-keyed cache gets wrong. **How to read this board** - Press Play: the walkthrough follows one pasted link through the cache, the guard and the fetch, then shows the two requests that must be refused. - THE PASTE (left) is the product; THE FETCH (middle) is a box that can reach only the public internet; THE INTERNET (right) holds the site and the two things that must never be reached. - 1 · Requirements, 2 · Capacity and 3 · Storage - including why thumbnails are 92% of the bytes. - 4 · Availability - why a preview is the most optional thing in the product, and must be treated that way. - 5 · How it works in prose, then 6 · Deployment - including how to test that the guard actually guards. - Finally the DATA MODEL, then argue with the weakness: the board trusts the page's own description of itself, and the first unfurl wins for a day. #### 1 · Requirements **Ask these before drawing anything** - Unfurl on paste or on send? On paste feels instant and leaks to the site that someone is composing; on send is private and the card arrives a second after the message. - Does the sender see and approve the card before sending? If yes, the sender can remove a misleading preview; if no, the card is the site's statement in the sender's voice. - Do we unfurl links in private messages, or only public channels? End-to-end encrypted products cannot unfurl server-side at all - the sender's device has to. - Should we fetch images, or only text? Images are 92% of the bytes and every one is an untrusted file going through a decoder. - Are there first-party providers (video sites, maps) with oEmbed endpoints we should prefer over scraping? - Can a domain owner opt out, and can our trust-and-safety team block a domain from unfurling within minutes? **Functional** - Given a URL, return a card: canonical URL, title, description, site name, thumbnail. - Never fetch a private, loopback, link-local or internal address, including via redirects or DNS changes. - Cap every fetch: 5 s total, 1 MB read, 5 redirects, text/html only for the page. - Proxy and re-encode thumbnails; the recipient never contacts the original site. - Cache cards by normalised URL; negative-cache failures. - Let senders remove a card and let moderators block a domain. **Non-functional** - Cache hit: p95 under 50 ms. Miss: card visible within 3 s at p95, or the message goes without one. - Zero successful requests from the fetcher to any non-public address - measured at the egress proxy, not asserted in code. - A preview failure never delays or fails a message. - A malicious page or image must not crash, hang or exhaust a fetcher worker. **Out of scope** - Client-side unfurling for end-to-end encrypted chats. - Malicious-URL classification (safe browsing lookups) - a separate check the card could carry a warning from. - Full-page archiving or screenshots. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps; a schema of 5 tables and 37 columns. ### Reviews and ratings - the average that must not be gamed - URL: https://tierslate.com/templates/reviews-ratings - Kind: system architecture board; level: core - Access: paid, 400 AI credits - the question is public, the worked answer is not Why a star rating is an aggregate maintained on write, and how fake reviews are held back before they count. What is hard about it, left in on purpose: The aggregate is maintained by deltas and repaired by a nightly recount, so for up to a day the number on the page can be off by whatever the delta pipeline dropped or doubled - and nobody is alerted unless the recount finds a gap larger than its threshold. The bigger gap is in time. The trust scorer catches most fake reviews before they count, but a review ring is often found weeks later, when its accounts are linked; removing 400 five-star reviews then corrects the average today, after the product spent those weeks ranked on the inflated number and the seller kept every sale it made. Nothing on this board claws that back or even records what the rating WAS while it was wrong. And holding reviews costs honest reviewers too: the 3% that go to a human wait a median of 18 hours, and a product with a real defect collects its one-star reviews in the hold queue while the page still says 4.7. #### Start here **Why this design** A star rating looks like an AVG() and is really a small ledger: a sum, a count and a histogram per product, maintained by deltas as reviews enter and leave the set that counts - and that set is defended, because every seller and competitor has a reason to change it. Interviewers use it to test write-time aggregation, idempotent event application, and whether you put moderation BEFORE the number rather than after. **What you will learn** - Why "4.6 from 12,408" is two integers read from one row, not 12,408 rows averaged on every view. - Why a new review is PENDING and only PUBLISHED reviews move the aggregate. - Why the trust scorer is a fan-out over four signals and answers publish, hold or reject. - Why each delta carries a per-product sequence number, so it applies exactly once. - Why a nightly recount exists, and what it can and cannot repair. - Why ranking uses a Bayesian average, so a product with one five-star review does not outrank one with 12,408 at 4.6. **How to read this board** - Press play once: the walkthrough follows one review from submit to the number on the page. - WRITE: verify, score, hold or publish. - AGGREGATE: state changes become deltas on one row per product. - READ: the page and search ranking both read that row and nothing else. - DATA MODEL: reviews with the star value they currently contribute, and the aggregate row. - Then pages 1 to 6 in order: requirements, capacity, storage, availability, how it works, deployment. #### 1 · Requirements A shopper wants a number they can trust in under a second. A seller wants that number to go up. The system's job is the first one despite the second. **Ask these before designing** - Who may review? Anyone, or only buyers? "Verified purchase" is usually a badge and a trust signal, not a gate - gating loses gifts, in-store purchases and returns. - How fast must a review appear? Seconds for most is expected. A minority held for hours is the price of not publishing fakes. - Can reviews be edited? Yes - and an edit from 5 to 2 stars must move the aggregate by -3, exactly once. - What does the page show: mean, histogram, or both? Both, and search ranks on something else (a Bayesian average). - What happens when fraud is found later? Reviews are removed and the aggregate is corrected. Is the history of the rating kept? **Functional** - Submit one review per user per product: 1-5 stars, title, text, optional photos. - Score every review for trust; publish, hold for a human, or reject. - Show the rating, the count and a five-bar histogram on every product page. - Show the top reviews sorted by helpfulness; users vote helpful. - Edit and delete your own review; moderators remove reviews. - Rank search results by a rating that resists small samples. **Non-functional** - Product page rating read p99 under 20 ms, at 70,000 page views/s peak. - 85% of reviews published within 10 s of submit; held reviews decided within 24 h. - Aggregate exactly right after the nightly recount, and never more than 0.5% of count off before it. - Every state change applied to the aggregate exactly once. - Read availability 99.99%: a page without stars converts measurably worse. **Out of scope** Q&A, seller responses, photo moderation models, review syndication between sites, incentivised review programmes. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps; a schema of 5 tables and 36 columns. ### Content moderation - deciding before everyone sees it - URL: https://tierslate.com/templates/content-moderation - Kind: system architecture board; level: core - Access: paid, 400 AI credits - the question is public, the worked answer is not Why moderation is a fast automatic gate plus a slow human queue, and what is shown while it decides. What is hard about it, left in on purpose: The human review queue is staffed for an average day, and the grey-zone band is a fixed slice of all posts, so a breaking news event that triples posting triples the queue while reviewer headcount stays flat - limited posts then sit unranked for hours and the backlog is invisible to everyone except the authors. The thresholds that decide how much reaches humans are tuned from human verdicts, so a reviewer team that drifts lenient quietly teaches the classifier to send it less. And user reports are counted per post with no weighting for who reports, so a coordinated brigade of 200 accounts can push any post to the front of a queue meant for the most harmful content. #### Start here **Why this design** Every product that lets strangers post - comments, reviews, marketplace listings, chat, profile photos - needs moderation, and almost every first design gets it wrong the same way: it treats moderation as one function call that returns allowed or removed. In practice there are two very different deciders. A machine answers in a couple of hundred milliseconds and is confidently wrong at the edges; a person takes a minute, is expensive, and there are never enough of them. The design question is how to split the work between the two and, above all, what the post looks like to everyone while the slow one has not answered yet. **What you will learn** - Why known-bad media is caught by perceptual hash matching before any classifier runs, and why that one lookup is the cheapest, most accurate check on the board. - How two thresholds on a classifier score split traffic into allow, limit and remove, and how moving either one by 0.05 changes reviewer headcount by hundreds. - The LIMITED visibility state: visible to the author and followers, excluded from ranking, search and recommendations until a person decides. - Why the review queue is ordered by expected harm (severity times projected reach), not by arrival time. - How reviewer verdicts become labelled data, and the feedback loop that can make the classifier drift towards whatever the reviewers already do. - Appeals as a second pass by a different reviewer, and why a decision is an append-only log row rather than a flag on the post. **How to read this board** - Press Play. The walkthrough follows one post: SUBMIT on the top left, through the FAST GATE, into VISIBILITY on the right. - Then the SLOW QUEUE along the bottom - reports, reviewers, decisions, appeals - and the dashed wire back to the gate that retrains it. - 1 · Requirements - what must be decided synchronously and what may wait. - 2 · Capacity and 3 · Storage - why the classifier fleet is the easy part and the reviewer count is the hard one. - 4 · Availability - what the gate does when a detector is down. - 5 · How it works, then 6 · Deployment plan, then the DATA MODEL at the bottom. #### 1 · Requirements **Ask these before drawing anything** - Which harms are in scope, and are any of them illegal everywhere? Child sexual abuse material and terrorist content have legal reporting duties and hard removal deadlines; spam and rudeness do not. Different policies get different thresholds, queues and retention. - Must a post be checked before anyone sees it, or may it publish and be cleaned up? Pre-moderation is safe and slow; post-moderation is fast and leaves screenshots. The answer is usually different per surface. - How long may the author wait on the submit button? Past about one second people resubmit, and you moderate the same post twice. - Is there a legal clock? The EU Digital Services Act requires a statement of reasons for every restriction and a working appeal route; Germany's NetzDG gave 24 hours for manifestly illegal content. - What does the author see when their post is held or removed, and can they appeal? **Functional** - Every new post and media item passes an automatic gate before it is distributed. - The gate returns one of four outcomes: allow, limit, remove, or remove-and-report (for the categories with legal reporting duties). - Uncertain posts and user-reported posts enter a human review queue ordered by expected harm. - Every restriction produces a decision row with the policy, the evidence and who or what decided, and a notice to the author. - Authors can appeal; an appeal is decided by a different reviewer than the original. **Non-functional** - Gate latency 300 ms at p99, including media hashing, for text and images. Video is checked on the first frames synchronously and fully in the background. - Known illegal media matched against the hash list: recall above 99 percent, zero tolerance for being distributed. - Grey-zone posts reviewed within 4 hours at p90 on a normal day; reports on the highest severity class within 30 minutes. - The gate fails safe per policy - never silently allow everything because one model is down. **Out of scope** - Writing the policies themselves. The system enforces a policy table; deciding what is harmful is a trust and safety team's job. - Account-level abuse (fake accounts, spam rings). It feeds this system a signal, it is not this system. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps; a schema of 5 tables and 35 columns. ### Stories - posts that delete themselves in 24 hours - URL: https://tierslate.com/templates/ephemeral-stories - Kind: system architecture board; level: core - Access: paid, 400 AI credits - the question is public, the worked answer is not Why expiry is a read-time filter plus a background sweep, and why the viewer list is the expensive part. What is hard about it, left in on purpose: The viewer list takes one write per view - about 15 billion a day, 520,000 a second at peak - which is 25 times the write rate of the stories themselves, and a celebrity story concentrates millions of those writes on one story id. Expiry is only exact on paths that apply the read-time filter: the CDN keeps serving a cached rendition until its own TTL runs out, a client that downloaded the video still has it, and the sweep that actually deletes bytes runs minutes to hours behind, so "gone after 24 hours" is true for the product and not yet true for the disk. And the tray multigets up to 400 story-head keys per open, so its p99 is the p99 of the slowest cache shard it touched. #### Start here **Why this design** Stories - Instagram, Snapchat, WhatsApp Status, LinkedIn and many others shipped them - look like a feed with a timer. The timer is the interesting part. "Disappears after 24 hours" sounds like a TTL on a row, but no database deletes on time, and a story visible at 24 hours and 4 minutes is a broken promise that users notice and screenshot. The design answer is that expiry is two separate mechanisms with two separate guarantees: a filter that makes expired stories invisible exactly on time, and a sweep that makes them gone some time later. Then there is the part nobody expects to be expensive: the list of who watched each story. **What you will learn** - Why visibility at 24 hours is enforced by WHERE expires_at > now() on every read, and deletion by a background sweep that may lag. - Why the tray ("who has a story for me") is assembled at read time from a story-heads cache rather than pushed into every follower's inbox. - Why the viewer list is 25 times the write volume of the stories, and how idempotent inserts and hourly partitions keep it bounded. - How signed CDN URLs that expire with the story stop new fetches, and why they do not recall bytes already cached. - Why highlights keep a story past 24 hours by reference, and what that does to the sweep. **How to read this board** - Press Play and follow one story from POST through STORE, then a viewer opening the tray in WATCH. - Then VIEWER LIST, which is where the writes go, and THE SWEEP at the bottom. - 1 · Requirements, 2 · Capacity, 3 · Storage - the views and the media are the two big numbers. - 4 · Availability - what a late sweep or a slow cache shard costs. - 5 · How it works, 6 · Deployment plan, then the DATA MODEL. #### 1 · Requirements **Ask these before drawing anything** - Exactly 24 hours, or "about a day"? The answer decides whether a TTL alone is acceptable. It almost never is. - Who sees a story - followers, close friends, a custom list? Audience is checked at read time, so changing a list after posting changes who can see it. - Does the author see who viewed, and for how long after expiry? Instagram keeps the list visible for 48 hours. - Can a story be kept (archive, highlights)? If yes, "deleted at 24 hours" becomes "removed from the public tray at 24 hours", and the legal wording matters. - What does delete mean for compliance - must bytes be gone from backups and caches, and by when? **Functional** - Post a photo or a video of up to 15 seconds; it is visible to its audience for exactly 24 hours. - Open a tray of accounts with live stories, unseen ones first. - Record each view once per viewer per story and show the author a viewer list with a count. - Optionally pin a story to a highlight, which keeps it beyond 24 hours on the author's profile. **Non-functional** - Expired stories never returned by any API after expires_at, to the millisecond. - Tray open at p99 under 200 ms; first frame of a story under 400 ms. - Media bytes of an expired, unpinned story deleted from origin within 6 hours, and CDN copies unreachable within 1 hour. - A lost view beacon is acceptable; a doubled view is not (counts are shown to authors). Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps; a schema of 5 tables and 24 columns. ## Messaging & realtime ### Chat and messaging - one socket, many deliveries - URL: https://tierslate.com/templates/chat-messaging - Kind: system architecture board; level: core - Access: paid, 400 AI credits - the question is public, the worked answer is not Delivery guarantees, presence and ordering across devices. What is hard about it, left in on purpose: The connection tier is stateful and nothing else here is. A gateway holds its sockets in memory, so it cannot be rolled or scaled like the stateless services beside it - deploying gw-1 hangs up on everybody on gw-1 - and the presence registry that is the only record of which gateway holds you is a single store with no replica behind it. #### Start here **Why this design** Chat is the standard question for testing whether a candidate can let go of request/response. The send path is easy; the hard parts are that the server must reach a person who asked for nothing, that the tier holding those connections is stateful while it looks like every other service, and that "delivered" is a claim about a screen the system cannot see. Interviewers use it to probe delivery guarantees, ordering and fan-out; teams that run chat learn the same lessons from their first gateway deploy, which hangs up on a hundred thousand people at once. **What you will learn** - Why the connection tier is stateful, and why a sticky load balancer balances once per connection rather than once per request - so load does not rebalance itself. - Persist first, then fan out: why the message store is the delivery guarantee and every hop after it may fail and be retried. - How a per-conversation sequence number (not arrival order, not a clock) gives every device the same order, and how gaps are detected and filled. - The fan-out arithmetic - one send becomes ~5.6 device deliveries - and why large groups switch from push to pull. - How the offline path works through APNs/FCM, and why a push notification is never a delivery receipt. - How to roll a stateful gateway fleet: drain, jittered reconnects, admission control, and why the gateway should change rarely. **How to read this board** - Diagram, left to right: PEOPLE (a sender with an open socket, a recipient on two devices), then CONNECTION TIER · STATEFUL (Edge LB, gw-1, gw-2 and Presence), then CORE · STATELESS (Chat service, Message store, Fan-out, Delivery queue, Receipt writer). - Then the band along the bottom, OFFLINE PATH · NO SOCKET TO PUSH DOWN: Push service and APNs / FCM, the only dashed wires. Press play for the eight-step walkthrough - one message, two endings. - Page 1 · Requirements - the questions to ask first, and the latency and delivery targets the rest of the design is held to. - Pages 2 and 3 · Capacity and Storage - messages per second, sockets, gateways, fan-out, and the bytes behind them. - Page 4 · Availability - what happens when a gateway dies, when Presence dies, and what is shed first. - Page 5 · How it works - the send path told end to end, including ordering, receipts and the offline device. - Page 6 · Deployment plan - rolling the one tier that cannot simply be restarted, and running it in several regions. #### 1 · Requirements **Ask these before drawing anything** - One-to-one only, or groups? How big can a group get? Group size is the fan-out factor, and above a few hundred members the delivery model has to change from push to pull. - Multi-device? If one person reads on a laptop and a phone, every message is delivered per DEVICE, receipts are per device, and a new device needs history - which decides whether the server keeps a log at all. - Does the server keep history, or only hold messages until they are delivered? Store-and-forward is cheaper and more private; a server-side log is what makes multi-device sync and a new phone work. This board keeps the log. - What does "delivered" mean - stored by us, received by a device, or painted on a screen? Pick one per tick and say it out loud. - What order must two devices agree on? Total order per conversation is achievable; a global order across conversations is neither achievable nor needed. - Is the content end-to-end encrypted? If yes the server routes opaque envelopes and cannot index, search or preview them, and push payloads carry no text. - Are presence ("online", "last seen") and typing indicators in scope? They are the highest-volume, lowest-value traffic and the first thing to shed. **Functional** - Send a text message to a person or a group of up to 1,024 members; receive it live on every connected device. - Deliver to devices that are offline via a platform push notification, and sync everything missed when the app next opens. - Show sent (stored by us), delivered (acked by a device) and read (rendered on screen) per recipient. - Keep a per-conversation history that a newly signed-in device can page backwards through. - Show online / last-seen presence and typing indicators, best effort. **Non-functional** - Send acknowledged ("sent" tick) only after a quorum write: p99 under 300 ms from the sender's socket. - Delivery to an online recipient in the same region: p50 under 100 ms, p99 under 500 ms end to end; cross-region p99 under 1 s. - Zero loss once "sent" is shown. Delivery is at-least-once; the client de-duplicates by message id, so the user sees exactly once. - Every device of every member renders a conversation in the same order (by sequence number), even when copies arrive by different paths. - Reconnect and catch up on 1,000 missed messages in under 2 s on a mobile network. - Sending: 99.99% available. Live delivery: 99.95%, falling back to sync-on-open when it is not. Presence and typing: no target - they may go dark. **Out of scope** - Media upload and storage, which is object storage plus a CDN and its own board. - End-to-end key exchange (the Signal protocol) - assumed to exist; this board routes envelopes. - Voice and video calls, search, spam and abuse classification. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps. ### Transactional email - the receiver decides - URL: https://tierslate.com/templates/email-delivery - Kind: system architecture board; level: core - Access: paid, 400 AI credits - the question is public, the worked answer is not Why getting an email into the inbox is a negotiation with a receiver whose rules you cannot see. What is hard about it, left in on purpose: The three records that decide whether any of this works - SPF, DKIM and DMARC - live in DNS, which has no canary, no test, no rollback and no alarm attached to it. A rotated DKIM key or an SPF include that quietly exceeded ten lookups breaks one hundred per cent of sending with a green build and a silent pipeline, and the first signal is a complaint rate three days later. The board makes it worse in two ways it draws honestly: marketing and transactional mail share one IP pool, so a campaign that crosses Gmail's 0.1% complaint threshold throttles the password resets too; and the suppression list, the only brake on that, is fed by an asynchronous feedback loop that is lossy by design - a complaint we never receive is an address we keep mailing. #### Start here **Why this design** Sending email is a queue and a retry loop; getting it into an inbox is a negotiation with a receiver that decides on rules it does not publish. Interviewers use this system to see whether you treat deliverability - authentication in DNS, suppression, IP reputation, bounce and complaint handling - as architecture rather than ops trivia. Real teams learn it the hard way: a forgotten SPF include or a marketing campaign over Gmail’s 0.1% complaint threshold stops the password resets with every dashboard green. **What you will learn** - Why the suppression check happens before the queue, fails closed, and is the one table you can never prune or lose. - How SPF, DKIM and DMARC work together, why they live in DNS outside any deploy pipeline, and how to rotate a DKIM key without a gap. - Why 4xx and 5xx must be handled differently: capped, jittered backoff bounded by a bounce deadline versus immediate permanent suppression. - Why IP warm-up and reputation are per (IP, receiving domain), and why sharing one pool between marketing and transactional mail is a mistake. - How 40M emails a day comes to only about 500 sends/s on average with a scheduled 6x campaign peak - and why storing template version plus variables instead of rendered HTML saves 113x storage. - How bounces, ARF complaints and DMARC aggregate reports flow back, asynchronously and lossily, to close the loop. **How to read this board** - Diagram, top row left to right, one message going out: COMPOSE · WHAT WE MAY SEND, AND TO WHOM (render, classify, suppression check), then QUEUE AND SIGN (send queue, DKIM signer, deferred queue). - Then OUT · OUR PART ENDS AT THE HANDSHAKE (MTA pool per domain, sending IPs, DNS records) and SOMEBODY ELSE'S RULES (the receiving MTA and the recipient). - Then the bottom band, WHAT COMES BACK · HOURS TO DAYS LATER, read right to left: DMARC reports, bounces and complaints, the event stream, and the wire back into the suppression list. Press play for the eight-step walkthrough. - Page 1 · Requirements - the questions to ask first (streams, domains, bounce deadline) and the latency, complaint-rate and no-silent-loss targets. - Pages 2 and 3 · Capacity and Storage - 463 sends/s average, a 2,918/s campaign peak, 25% complaint-rate headroom, and the 400 B versus 45 KB body decision. - Page 4 · Availability - DNS as the single point of failure, the suppression list failing closed, and why inbox placement has no SLA. - Page 5 · How it works - one message from API to inbox and back; Page 6 · Deployment - regions, DNS changes as a ritual, rollback by cache expiry. - Finally the DATA MODEL at the bottom: five tables, and why the 72 GB suppressions table is the irreplaceable one. #### 1 · Requirements **Ask these before drawing anything** - Transactional only, or marketing too? If both, the first real design decision is whether they share IPs, and the honest answer is that they must not. - Do we own the sending domain, or are we sending on behalf of customers? Customer domains mean per-customer DKIM keys, per-customer DNS instructions, and a support burden bigger than the system. - What is the latency promise? A password reset in 10 seconds and a weekly digest are different systems sharing a queue at their peril. - Do we store the rendered body? It is the difference between 16 GB a day and 1.8 TB a day - see the storage page. - What is the bounce deadline: how long may a deferred message keep retrying before delivering it is worse than dropping it? A password reset landing four hours late is a security event. - Are we required to track opens and clicks? Pixel tracking is a second traffic pattern, a privacy question, and increasingly a lie - Apple Mail Privacy Protection preloads images and fabricates opens. - Who owns unsubscribe, and does it apply across streams? A one-click unsubscribe from marketing must not stop a receipt. - What is the compliance retention on message content and on consent records? They are different answers and both belong in the schema. **Functional** - Accept a send request, render a versioned per-locale template, and deliver it. - Never send to a suppressed address, and check that before queueing. - Sign every message with DKIM and send from an IP whose SPF and DMARC alignment is correct. - Retry 4xx deferrals with capped exponential backoff; never retry a 5xx. - Process DSN bounces, ARF complaints and feedback-loop reports, and suppress accordingly. - Warm new IPs gradually, per receiving domain. - Record every outcome per message, per attempt. - Ingest DMARC aggregate reports and surface authentication failures. **Non-functional** - Transactional: p95 accept-to-first-SMTP-attempt under 5 seconds. - Marketing: a 20M-recipient campaign fully handed over within 2 hours. - 99.95% availability for accepting a send. Delivery to the inbox is not an availability target and cannot be one. - Exactly-once semantics at the address level via an idempotency key - a duplicated password reset is a security problem, not an annoyance. - Complaint rate below 0.1% per receiving domain, which is a hard architectural constraint, not a marketing KPI: above it, Gmail throttles everything from the IP. - No message is lost silently. Every message reaches a terminal state and that state is recorded. **Out of scope** - Inbound mail, parsing and threading - a different system entirely. - Campaign composition, audience selection and A/B assignment. - The MTA software itself; assume a hardened off-the-shelf one. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps; a schema of 5 tables and 35 columns. ### Presence - the most expensive cheap feature - URL: https://tierslate.com/templates/presence-service - Kind: system architecture board; level: starter - Access: paid, 200 AI credits - the question is public, the worked answer is not Why a green dot is a claim that expires, and why fanning it out costs more than every message sent. What is hard about it, left in on purpose: Presence costs more than the product it decorates. Heartbeats are 400,000 writes a second at peak for a boolean that usually did not change, and a status change is fanned out to everyone currently looking at that name - 7.8 billion pushes a day, about eighty times the message volume of the chat product next to it. Every instinct for making it more accurate makes it worse: halving the TTL doubles the beat rate, and persisting the state produces a durable record that is wrong the moment it is written, because a backup of a set of live TCP connections is a lie. The board also draws no cache on the bulk read, so 180 million app launches a day each fetch 240 keys uncached - 500,000 reads a second whose only purpose is to colour some dots. #### Start here **Why this design** The green dot looks like the cheapest feature in a chat product and is usually the most expensive one per bit of information. Interviewers ask for it because it cannot be answered with a database and a request: nobody ever tells you a person left, so presence has to be a claim that expires, and its cost is the product of two large numbers - everyone beating and everyone watching. Real messaging teams meet it as the line item that outgrows the messages. **What you will learn** - Why presence is a TTL, not a row: a 30-second heartbeat against a 45-second expiry, and an expiry sweeper that manufactures the offline event nobody sends. - How to size a write rate that depends on the size of the user base rather than on activity - 400,000 beats a second at peak, 99.3% of them carrying no change. - Why fan-out is the real bill: one transition times ~29 people currently watching that name is 7.8 billion pushes a day, about eighty times the message volume. - Where to filter for visibility (before the fan-out, never after) and why a one-second coalescer is the most valuable cheap box on the board. - Why the cold launch - 240 keys at once, 500,000 reads a second - is a different read shape, and why "unknown" and "offline" share one fallback. **How to read this board** - Press Play and follow the walkthrough once through the diagram: it travels THE BEAT (top left), THE TRUTH AND ITS EXPIRY, then FAN-OUT on the right. - Then the bottom row of the diagram: THE DURABLE HALF (what survives a socket) and THE WATCHER (an app that just opened and wants 240 answers). - 1 · Requirements - the questions that set the TTL, the stale budget and the privacy rules. - 2 · Capacity estimation and 3 · Storage estimation - where 400,000 writes a second and the 259 GB watch multiplier come from. - 4 · Availability - why presence may be wrong but must never leak. - 5 · How it works - the whole flow in prose, then 6 · Deployment plan - regions, and the TTL-before-interval rollout order. - Finally the DATA MODEL below the band, then argue with the weakness this board leaves in on purpose: the bulk read on launch has no cache in front of it. #### 1 · Requirements **Ask these before drawing anything** - How stale may a green dot be? Five seconds and five hundred milliseconds are the same feature to a user and a factor of ten in cost. - Is presence per user or per device? "Online on desktop, away on mobile" is a different data model and a different fan-out from one bit per person. - Who may see whom? Invisible mode, blocks and contacts-only turn every fan-out into a filtered fan-out, and filtering after the fan-out leaks. - Do we need "away" and "busy", or only online and offline? Every extra state is another transition and another fan-out. - Is last-seen a separate product promise? It survives everything else here and is the only presence fact worth putting on a disk. - What is the largest number of people one person watches? The average is 240; the answer for a support agent with a 5,000-contact roster is what actually sizes the read path. - What should happen during an outage - does everyone appear offline, or does everyone freeze at their last state? Both are defensible, and choosing afterwards means choosing badly. - Is presence load-bearing for anything else? The moment routing, billing or notification suppression reads it, it stops being allowed to be wrong, and the whole design changes. **Functional** - Record that a device is alive, from a periodic heartbeat. - Expire a device that stops beating, with no cooperation from it. - Aggregate devices into one per-user state: online if any device is. - Notify watchers when a user's state changes, subject to visibility rules. - Answer a bulk query for the state of up to several hundred users at once. - Fall back to "last seen at" when there is no live state. - Support invisible mode, which must be indistinguishable from offline. **Non-functional** - A transition is visible to watchers within 5 seconds at p95. Not 500 ms - the extra nine costs an order of magnitude and nobody can see it. - Cold-launch bulk query for 240 contacts: p95 under 400 ms. - 99.9% availability, and presence is explicitly permitted to fail. It must degrade to last-seen rather than page anybody. - Presence state is NOT durable and must not be. Rebuilding it from heartbeats takes one TTL interval. - Invisible mode is a correctness requirement, not a best effort: leaking it once is a privacy incident. **Out of scope** - Typing indicators - same fan-out shape, ten times the event rate, and worth a board of their own. - Location or activity presence ("listening to", "in a meeting"). - Using presence to route messages, which would make an eventually-consistent, deliberately-lossy system load-bearing for delivery. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps; a schema of 5 tables and 23 columns. ### Push notifications - the token outlives the install - URL: https://tierslate.com/templates/push-notifications - Kind: system architecture board; level: core - Access: paid, 400 AI credits - the question is public, the worked answer is not Why push failures are silent, and how token lifecycle keeps a dead token from counting as delivered. What is hard about it, left in on purpose: Delivery is measured at the provider's door, not at the phone. A send to a token belonging to a deleted app returns 200 and is counted as delivered, so a cohort can go completely dark while the dashboard stays at 99.9% - and nothing on the board can tell "delivered" from "accepted by a cloud that will drop it". The only correction is the feedback band, which is asynchronous, lossy, differently shaped per provider, and by far the least-exercised code here. It is also the most dangerous: the reaper retires tokens on a signal nobody verifies, so one mis-parsed 410 permanently un-notifies a real user with no error anywhere. And the batcher, which exists so a 25M broadcast does not open 25M connections, delays every transactional notification behind whatever marketing is sending. #### Start here **Why this design** Push looks like "call Apple, call Google" and is really a lesson in systems whose failures are silent. A send that fails is retried and nobody notices; a send to a dead token returns 200 and is counted as delivered. Interviewers use it to see whether you design the return path - token lifecycle, provider feedback, idempotency - and whether you can separate a 25M-user marketing broadcast from the one-time passcode that must arrive in seconds. Real notification teams are judged on exactly those two things. **What you will learn** - How one notification becomes 2.4 device sends, and why 400M notifications a day is 960M send attempts and a send log - not the notification - is what you size for. - Why the real peak is a step, not a curve: a broadcast to 25M users in ten minutes is 100,000 sends a second, nine times the daily peak, and autoscaling cannot catch a step. - Why transactional and marketing traffic need separate lanes, and what a batcher costs the message that is queued behind a campaign. - How to read provider responses correctly: a 429 means slow down and retry, a 410 means the token is dead and must be retired - and retiring a live token by mistake silently un-notifies a real person. - Why the feedback band is the only thing keeping 34,000 dead tokens a day out of the registry, and why "delivered" on a dashboard means "accepted by the provider". **How to read this board** - Press Play and follow the walkthrough along the top: DECIDE (who, which devices, may we), then SEND (the conveyor belt), then NOT OURS (APNs and FCM) and THE PHONE. - Then read the FEEDBACK band underneath it right to left - that is the direction the truth travels, hours later. - 1 · Requirements - the questions that separate a passcode from a promotion. - 2 · Capacity estimation and 3 · Storage estimation - the 2.4x device fan-out, the broadcast step and why the send log is the big table. - 4 · Availability - what "delivered" can and cannot mean when the last hop is somebody else's cloud. - 5 · How it works, then 6 · Deployment plan. - Finally the DATA MODEL below the band, and then the conversation this board leaves open on purpose: the reaper retires tokens on a signal nobody verifies. #### 1 · Requirements **Ask these before drawing anything** - Are these notifications transactional, marketing, or both? If both, they must not share a queue, and the reason is on the capacity page. - What is the latency promise? "A 2FA code within 5 seconds" and "a digest sometime this evening" are two systems, and batching is fatal to the first. - Do we need to know whether a notification was SEEN, or only that it was accepted? Seen requires client telemetry and changes the data model. - How do we define a dead token, and how long may a token stay dead before we notice? This is the number the whole board is built around. - Can the payload contain personal data? If yes it crosses a third-party cloud, which is a compliance question before it is an engineering one. - Is there a per-user rate limit or a daily cap? Without one, every team with API access can burn the notification channel for everyone else. - Web push and email fallback: are they in the same system? Sharing preferences across channels is easy; sharing delivery is not. - What happens when the provider is down for an hour - do we drop, delay, or deliver late? A stale notification can be worse than none. **Functional** - Accept a send for a user and deliver it to every eligible device they own. - Respect per-category preferences, quiet hours and mutes; suppress when the user has already seen the thing on another surface. - Collapse repeated notifications for the same subject into one (collapse keys). - Retry transient provider failures with backoff; never retry a permanent one. - Detect and retire dead tokens, and stop sending to them. - Register, rotate and de-duplicate device tokens. - Report, per notification, what happened as far as we can honestly know. **Non-functional** - Transactional: p95 from accept to handed-to-provider under 2 seconds. - Marketing: a 25M-user broadcast fully handed over within 30 minutes. - 99.9% availability for accepting a send. Delivery itself is explicitly not ours to promise, and the SLA says so. - At-most-once per collapse key per window; duplicates are worse than lateness here, because a duplicate is visible on a lock screen. - Dead tokens retired within 24 hours of the provider telling us. **Out of scope** - Composing the copy, localisation, and A/B assignment - upstream of the API. - In-app inbox and web push. - Deciding WHETHER something is worth notifying about, which is a product problem that no amount of infrastructure fixes. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps; a schema of 5 tables and 39 columns. ### Team channels - the unread count is the hard part - URL: https://tierslate.com/templates/slack-channels - Kind: system architecture board; level: core - Access: paid, 400 AI credits - the question is public, the worked answer is not Why a 30,000-member channel is not a big DM, and why the unread badge is the hardest number to keep. What is hard about it, left in on purpose: Unread counts are computed, not stored - last_read_seq per (user, channel) subtracted from the channel head - which makes a message one write instead of thirty thousand. The board then pays for it on every app launch: 12M people x 6 launches x 35 channels is 2.5 billion range counts a day, 2.2x more work than the counter writes it avoided, and the read_state table still takes six times as many writes as the message table because moving a marker is itself a write. Worse, mentions cannot be computed this way - a mention badge has to be exact - so the expensive per-user row exists anyway, just for the small case. And nothing on this board reconciles two devices reading the same channel at once: last_read_seq is last-writer-wins, so reading on your phone can mark a channel read on your laptop mid-scroll. #### Start here **Why this design** Team chat is the classic interview question that looks like "store messages, push them to sockets" and turns out to be about a number in a sidebar. Sending is short and identical every time; the hard parts are a fan-out whose cost depends on the size of the room - a two-person DM and a 30,000-member #general are not one problem - and an unread count that every launch needs for every channel. Interviewers use it to see whether you size reads as carefully as writes; chat teams live with it because the badge is the first thing users notice when it is wrong. **What you will learn** - Why a per-channel sequence number (seq, unique within the channel) is the backbone: ordering, gap detection, history paging and unread counts all fall out of it. - How to split fan-out by room size: push every message into a small room, but broadcast a 30,000-member room only to the 2-4% of members currently watching it. - The unread-count trade in numbers: a counter per member is 1.15 billion writes a day; a read marker plus head-minus-marker is one write per message - and 2.5 billion range counts a day on launch. - Why mentions cannot use the same trick: a mention badge must be exact, so the per-user row exists anyway, just for the small case. - How a thread reply avoids bumping the room, and what that does to the data model. **How to read this board** - Press Play and follow the walkthrough: PEOPLE on the left, the WRITE PATH and THE LOG across the top, then FAN-OUT underneath. - Then the right-hand column: UNREAD (the count nobody wants to store) and THREADS (replies that do not bump the room). - 1 · Requirements - the questions that decide the fan-out and the badge. - 2 · Capacity estimation - the unread count worked both ways; then 3 · Storage estimation. - 4 · Availability - what may lag (a badge) and what may not (the order of a channel). - 5 · How it works, then 6 · Deployment plan. - Finally the DATA MODEL below the band - read_state is the table that hurts - and then the open question this board leaves on purpose: last_read_seq is last-writer-wins across your devices. #### 1 · Requirements **Ask these before drawing anything** - How big may a channel get? The answer picks the fan-out strategy, and there is no strategy that is right at both 8 members and 30,000. - Must the unread count be exact, or may it be approximate while you are not looking at the channel? This is the single highest-leverage question on the page. - Must a mention badge be exact? Almost certainly yes, and that is a different answer from the one above, which is why two mechanisms exist. - Is history infinite, or does a plan tier cap it at 90 days? Retention changes the message store from a growing cost into a constant. - One read state per person, or per person per device? Per device is what users expect and doubles the most-written table on the board. - Do threaded replies mark the parent channel unread? Getting this wrong makes a busy thread feel like spam to 29,000 people. - Do we need full-text search across all of it? Search is a second copy of the message store and it should be sized here rather than discovered later. - Guest and shared-channel access: can a member of workspace A read a channel owned by workspace B? Membership stops being a simple join the moment the answer is yes. **Functional** - Post a message to a channel or a DM; deliver it to anyone currently looking. - Show a per-channel unread state and an exact mention badge in the sidebar. - Reply in a thread without marking the channel unread for people not in the thread. - Mute a channel: it receives messages and generates no badge. - Open the app after nine hours and get an accurate sidebar in under a second. - Scroll back through history from any point in a channel. **Non-functional** - p50 delivery to a watching socket under 300 ms; p99 under 1 s. - Sidebar hydration on cold launch: p95 under 800 ms for 35 channels. - 99.95% availability for posting. Unread counts may be stale under load; mentions may not be wrong. - Per-channel ordering is guaranteed. Cross-channel ordering is not, and no part of the product depends on it. - A message, once acknowledged, is never lost. A count, once wrong, self-heals on the next read. **Out of scope** - Search indexing (a second system, sized separately). - Calls, huddles, file previews. - Compliance export and legal hold, which want their own read path over the same log. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps; a schema of 5 tables and 33 columns. ### Encrypted delivery - one tick, two ticks, blue - URL: https://tierslate.com/templates/whatsapp-delivery - Kind: system architecture board; level: deep - Access: paid, 800 AI credits - the question is public, the worked answer is not What changes when the server cannot read the message, and why every tick after the first is a device claim. What is hard about it, left in on purpose: The ticks are claims the server cannot check. Tick two and tick three are produced by the recipient's device and vouched for by nobody the server can audit, so a patched client shows blue ticks it never earned and a battery-saving OS shows none it did - and the server, holding only ciphertext, has no way to tell the two apart. The offline mailbox is worse: it is undecryptable bytes with no expiry policy drawn, so it can never be deduped, compacted or re-encrypted, and the retention period is a guess worth four orders of magnitude of storage. And because the server never held a key, a device added tomorrow gets no history at all - the fix for that, a sender-side history transfer, is not on this board. #### Start here **Why this design** End-to-end encrypted messaging is the chat-system question with the server’s eyes taken away. Interviewers use it to see whether you notice that encryption changes the architecture and not only the security posture: fan-out moves onto the sender’s phone, the server can no longer dedupe, index or re-render, and every delivery status after the first is a claim made by a device. Real messengers live with exactly these consequences - multiple devices per person, offline mailboxes, and receipts that outnumber messages. **What you will learn** - Why a message to one person with three devices is three ciphertexts from the sender’s phone, and how 8 billion messages a day become 32 billion envelopes - 1.11M writes a second at peak. - What each tick actually means: our queue accepted the bytes, a device stored them, a device claims a human looked. - Why receipts - 64 billion a day, 740,000 a second - outnumber messages eight to one and must merge as a set that only grows. - How one retention sentence for undelivered mail moves the message store from 160 GB to 87 PB. - Why the key directory is the one place the server is trusted, and why safety numbers exist because of it. - How a push notification wakes a device without carrying a body, and why the connection edge is drained rather than restarted. **How to read this board** - The diagram follows the message left to right: THE SENDER · WHERE PLAINTEXT LIVES, then OUR SERVERS · ENVELOPES ONLY, then ONE PERSON · TWO DEVICES. - Then the two bands that come back: WAKE-UP · FOR A DEVICE WITH NO SOCKET, and THE THREE TICKS · CLAIMS, NOT FACTS. Press play for the eight-step walkthrough, which starts before anything is typed. - Page 1 · Requirements - the questions, the first of which moves every box on the board. - Pages 2 and 3 · Capacity and Storage - envelopes, receipts and retention. - Page 4 · Availability - the single points of failure, and what degrades rather than dies. - Page 5 · How it works, told end to end, then Page 6 · Deployment plan - home regions, the edge drain, two-release format changes. - The DATA MODEL at the bottom: five tables, and not one of them has a body column. - Finish on the deliberate weakness: the ticks are client claims the server cannot verify, the mailbox has no expiry, and a new device gets no history. Decide which of the three you would fix first, and what it costs. #### 1 · Requirements **Ask these before drawing anything** Every one of these questions moves a box on the diagram. The first one moves all of them. - Is the server allowed to read a message body? If yes, half this board disappears and search, backup and web clients become easy. If no, fan-out moves onto the sender's phone and stays there. - One device per person or several? Multi-device is what turns a message into N ciphertexts and makes "history on a new device" a separate product. - What ordering do we promise, and across what? Per conversation is achievable. Per sender across devices is not, without a sequencer the sender trusts. - Can a user switch read receipts off? If so the third tick is optional and the UI must be honest about never getting it. - How long do we hold an undelivered message - 30 days, or forever? This single sentence is worth four orders of magnitude of storage. See the storage page. - How large may a group be? 1,024 members is a different fan-out problem from a pair, and the sender's uplink pays for it. - Do we owe anyone server-side search, backup or a browser client? All three need plaintext, so all three become client features or become a compromise. **Functional** - Send a message from one device to every device the recipient owns. - Deliver immediately to a device that holds a connection; hold it durably for one that does not. - Report three states to the sender: accepted by the server, received by a device, read by a human. - Groups up to 1,024 members, with the same guarantees as a 1:1 chat. - Carry media without putting bytes on the message path. - Let a new device register keys and begin receiving new messages. **Non-functional** - The server must never hold a key that can open a message body. This is a constraint on the design, not a feature of it. - p99 send-accepted under 300 ms on a mobile network. - p50 delivery to a connected device under 500 ms. - 99.95% availability for accepting a send; 99.9% for delivering to a device that is connected. - An undelivered message survives the loss of an availability zone. - At-least-once delivery; the client deduplicates on message id. **Out of scope, and why** - Server-side search and server-side backup: both need plaintext we do not have. - Voice and video: a different transport with different failure modes. - History for a newly added device: needs a sender-side transfer, which is a product on its own. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps; a schema of 5 tables and 33 columns. ### Webhook delivery - retrying into somebody else's outage - URL: https://tierslate.com/templates/webhook-delivery - Kind: system architecture board; level: starter - Access: paid, 200 AI credits - the question is public, the worked answer is not Why a webhook is a promise to keep calling until the receiver answers, and how retries avoid becoming an attack. What is hard about it, left in on purpose: Every customer shares one delivery queue and one worker pool with no per-endpoint concurrency cap: one large customer whose endpoint hangs to the 10 second timeout ties up thousands of workers and delays every other customer's webhooks - a noisy neighbour the board does not isolate. There is no circuit breaker either, so an endpoint that has failed for an hour still gets every new event tried at full rate, and when it recovers its whole backlog is released at once. Delivery is at-least-once, so receivers get duplicates and must dedupe on the event id themselves. #### Start here **Why this design** Payment providers, code hosts, CRMs and chat tools all send webhooks: an HTTP POST to a URL the customer registered, saying "this happened". It looks like one outbound request. It is really a delivery system with a strange dependency - the most important component, the receiver, belongs to somebody else and fails whenever it likes. The design question is how to keep a promise to a server you cannot see, without hammering it when it is down and without letting one broken customer slow down all the others. **What you will learn** - Why the event is stored before any delivery is attempted (at-least-once, never at-most-once). - How 75M deliveries a day and a 300 ms median response turn into ~1,000 requests in flight - and 7,000 when one big customer hangs. - Exponential backoff with jitter: eight attempts over about a day, and why the jitter matters as much as the backoff. - Why receivers get duplicates, and how a signature and an event id let them verify and dedupe. - The noisy-neighbour problem, and the per-endpoint limits and circuit breakers this board leaves out. **How to read this board** - Top: EVENTS IN. Your product emits a domain event; the dispatcher looks up who subscribed and creates one delivery per endpoint. - Middle: THE ATTEMPT LOOP. The delivery queue, the red shared worker pool, the retry scheduler, and on the right, outside the boundary, the customer endpoint. - Bottom: RECORDS. Every attempt is logged; failed-for-good deliveries go to dead letters; the developer sees both in a dashboard and can replay. - Press play, then read pages 1 to 6, then the tables. The conversation to have is the red box: what happens to customer B when customer A is down? #### 1 · Requirements **Functional** - Customers register endpoints (an HTTPS URL) and choose which event types each receives. - When an event happens, POST a JSON payload to every subscribed endpoint. - A 2xx response within 10 seconds is success. Anything else - timeout, connection refused, 4xx, 5xx - is retried. - Retry with backoff for about 24 hours (8 attempts), then mark the delivery failed and keep it for manual replay. - Sign every payload (HMAC-SHA256 with a per-endpoint secret) and include a timestamp so receivers can reject forgeries and replays. - A dashboard showing every attempt, with response code and time, and a "resend" button. - Disable an endpoint automatically after several days of continuous failure, and email the owner. **Non-functional** - At-least-once: an accepted event is never silently dropped. Duplicates are allowed and documented. - First attempt within 5 seconds of the event, p99. - One customer's failing endpoint must not delay another customer's deliveries - stated here, and NOT met by this board. - Never make our retries a denial of service against a recovering customer. - Ordering is not guaranteed - each event carries its own timestamp and id. **Out of scope** - Ordered delivery per endpoint (needs one in-flight delivery per endpoint, much slower). - Exactly-once delivery - impossible across a network to a server we do not control. - Inbound webhooks (receiving them). Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps; a schema of 5 tables and 26 columns. ### Notification router - one event, the right channel, once - URL: https://tierslate.com/templates/notification-router - Kind: system architecture board; level: core - Access: paid, 400 AI credits - the question is public, the worked answer is not How one event is turned into email, push or SMS by preferences and quiet hours, without sending it twice. What is hard about it, left in on purpose: The claim makes a replay harmless but not a crash: a sender that dies after the provider accepted the message and before it marks the claim sent leaves a row stuck in "claimed", and the recovery sweep must either resend it (a duplicate) or drop it (a loss) without knowing which happened - the board resends, so "once" means once except in a window of about 200 ms per send. The push-to-email fallback is a second notification by design, triggered by "not opened in ten minutes", and push cannot tell "not opened" from "not delivered", so users who simply saw the banner and ignored it get the email too. Quiet hours are computed from a timezone saved at signup, so a traveller is woken at 3am. And everything deferred overnight is released at 08:00 local, so each timezone produces a morning spike of about 20,000 sends a second unless the release is smeared - which the deferred queue does with jitter, and the SMS provider's per-second rate limit still caps. #### Start here **Why this design** Every product with users ends up with a notification system, usually by accident: each team calls the email provider directly, then the push provider, then somebody adds SMS for one feature. Within a year users are getting the same alert on three channels at 2am and support cannot say why. A notification router is the one place that decides channel, timing and deduplication for every notification in the company. Interviewers like it because it mixes product rules (preferences, quiet hours, digests) with a real distributed systems guarantee (once per channel over an at-least-once bus). **What you will learn** - Separating "what happened" (an event) from "who should hear about it, how, and when" (routing), and why producers must never pick channels. - Preferences and consent as data: per notification type, per channel, with legal opt-in for SMS and marketing. - Quiet hours in the user's timezone, deferral until morning, digests, and the morning thundering herd they create. - Exactly once per channel via a unique claim, and the crash window that remains. - Fallback across channels, and why it is a deliberate duplicate. **How to read this board** - Press Play and follow the walkthrough: SOURCES on the left, then the ROUTE column hanging down from the router. - Then ONCE in the middle - the claim between deciding and sending. - Then CHANNELS and providers on the right, and the user. - 1 · Requirements to 6 · Deployment in the written band, then the DATA MODEL. - Then argue with the tension: once except for a crash window, a fallback that duplicates on purpose, and a timezone that travels. #### 1 · Requirements **Ask these before drawing anything** - Which notifications are transactional (password reset, payment failed, security alert) and which are engagement or marketing? The two have different legal rules, different quiet-hours rules, and must never share a send queue. - Who decides the channel - the producing team or the user? The answer must be the user, through preferences, with the notification type providing defaults. - What does "once" mean - once per event, or once per channel per event? A shipped order on push AND email is usually intended. The same push twice never is. - Is SMS in scope, and who pays for it? At a cent or more per message, SMS is reserved for opt-in, critical notifications. - How fresh must a preference change be? If a user turns off a type and gets one more five seconds later, is that a bug report? **Functional** - Consume domain events (order.shipped, comment.created, login.new_device) from the event bus. - For each recipient, select channels from type defaults, user preferences and consent. - Apply quiet hours in the user's timezone; defer non-urgent notifications to 08:00 local. - Collapse bursts of low-priority notifications into a digest within a 15 minute window. - Send each notification at most once per (event, user, channel), even when the event is delivered more than once. - Fall back from push to email when a push is not opened within ten minutes, for types that opt in. - Record delivery receipts, bounces, opens and unsubscribes. **Non-functional** - Transactional notifications: event to provider handoff in under 5 seconds at p99. - Engagement notifications: under 15 minutes, or the next 08:00 local. - Preference changes take effect within 30 seconds. - Duplicates per channel below 1 in 100,000 sends. - Unsubscribe honoured immediately and auditable for regulators (CAN-SPAM, GDPR, TCPA for SMS). Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps; a schema of 6 tables and 44 columns. ## Video & media ### Image CDN - resize on the fly, or store every size - URL: https://tierslate.com/templates/image-cdn - Kind: system architecture board; level: starter - Access: paid, 200 AI credits - the question is public, the worked answer is not Why the cache key is the design, and why egress and the long tail of misses set the bill, not CPU. What is hard about it, left in on purpose: Transforms are computed on demand, so the variant a designer invents on Tuesday exists on Tuesday with no backfill - and every distinct URL is a new cache key, a new AVIF encode at 250 ms of CPU, and a new object to store. The signing service is therefore the only thing between this design and an unbounded bill, which means an availability problem in a tiny stateless service becomes a cost problem in the largest one. Pre-generating instead bounds the spend at 36 variants per master, and buys that bound with storage for the 80 percent of variants nobody ever requests and a re-generation campaign every time the design system changes a breakpoint. The board also admits a third cost neither strategy fixes: 70 percent of distinct URLs are requested once a week, so they miss on arrival, pay full transform price, get cached, and are evicted before a second request ever comes. #### Start here **Why this design** An image service is the cleanest test of whether you understand caching economics rather than caching mechanics. Interviewers use it to probe who is allowed to mint a cache key, what happens to the 4% of requests that miss, and whether you notice that egress - not CPU - is the bill. Every team serving user content at scale, from a CDN’s image product to an e-commerce catalogue, lives inside the trade between resizing on the fly and storing every size. **What you will learn** - Why the cache key is the design: 6 widths x 3 formats x 2 DPRs bounds a master at 36 variants, and free-form parameters turn 3.6 billion keys into 60 trillion. - How a 96% edge hit ratio and a coalescing shield leave 830 transforms/s - about 208 busy cores for 1.2% of the traffic - and why a slip to 92% doubles that fleet. - Why egress (~$2.0M/year) dwarfs transform CPU (~$73k/year), which makes the 250 ms AVIF encode a bandwidth argument, not a quality one. - Why Vary: Accept must be normalised to three values before it touches a key. - Why signed, named presets fail closed, and why you version the key instead of purging. - Why the long tail - 70% of URLs asked once a week, 64% of transforms - is a cost no cache tuning removes. **How to read this board** - Press play once: the walkthrough follows one upload and one request, hop by hop, then the bill. - PUBLISH - ONE MASTER, WRITTEN ONCE (top left): hashed, deduplicated, immutable - and the srcset the app writes. - PRE-GENERATE - THE EAGER HALF (top right): the strategy that is drawn but mostly unused. - REQUEST - EACH HOP IS A SMALLER NUMBER AND A BIGGER PRICE: left to right, negotiation, edge, shield, transform, encoder. - THE BILL - KEYS, PURGES AND THE TAIL: signing, versioning and the telemetry that exposes the tail. - DATA MODEL - WHAT GENERATE SQL READS: variant_specs.pregenerate and variants.last_hit_at settle the eager/lazy argument. - Then pages 1 to 6 in order: requirements, capacity, storage, availability, how it works, deployment. #### 1 · Requirements One master image in, the right bytes out for a phone, a laptop and a retina display, in the best format each of them can decode - without the people writing the HTML having to think about any of it. **Functional** - Store one immutable master per image and serve derived sizes from it. - Serve a width-appropriate variant to any device, driven by the srcset the publishing app emits. - Negotiate format: AVIF where supported, WebP where not, JPEG as the floor. - Support a named set of transforms - crop, fit, quality - and reject anything outside it. - Invalidate an edited image everywhere within seconds. - Report, per publisher, how much egress and how much transform CPU their images cost. **Non-functional** - Edge cache hit ratio above 95 percent. This is the top-line metric: everything else on the board is what happens in the 4 percent. - p95 latency 30 ms on a hit, 400 ms on a cold transform. A cold AVIF encode is 250 ms of CPU and there is no way around that. - Availability 99.99 percent - an image service that is down makes every page on every customer site look broken, which is a worse perception than an outage of the site itself. - The number of distinct cache keys must be BOUNDED and countable. An unbounded key space is an unbounded bill. - A variant must never be served for the wrong image. Cache-key collisions here are a content-correctness bug that looks like a security incident. **Questions to ask before designing this** - Who controls the URLs - us, or the customer? If a customer can write any parameter combination they like, the transform fleet is sized by their imagination and not by our traffic. - How many distinct widths does the design system ACTUALLY use? Six named breakpoints is a bounded problem; continuous width parameters is an unbounded one, and everybody says six until you measure. - What is the edit rate on published images? If images are effectively immutable, version the key and never purge. If they change hourly, the purge path is the hard part of the system. - Is there a long tail, and how long? 70 percent of URLs requested once a week means most transforms never amortise and the cache hit ratio flatters you. - Do we serve user-uploaded images to the public? Then a decode bomb - a 40,000 by 40,000 pixel PNG that expands to gigabytes in memory - is a denial of service, and limits belong in the requirements rather than in an incident review. - Is animation in scope? An animated AVIF is two orders of magnitude more expensive to encode than a still, and it will arrive through the same URL shape. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 11 steps; a schema of 5 tables and 34 columns. ### Live streaming - latency against the buffer that saves you - URL: https://tierslate.com/templates/live-streaming - Kind: system architecture board; level: deep - Access: paid, 800 AI credits - the question is public, the worked answer is not Why segment length sets latency, resilience and cost at once, and what each second removed costs. What is hard about it, left in on purpose: The board buys 8 seconds of glass-to-glass latency with a rolling window of three 2-second segments, and those two facts are the same fact: the player is three segments behind live BY CONSTRUCTION, and those three segments are also the entire cushion it has when a Wi-Fi router stutters. Halving the segment length halves both. Chat arrives over a websocket in 200 milliseconds, so every viewer learns about the goal from the chat window seconds before they see it - and the 2-second choice also writes 125,000 objects a second across the platform, which is 54,000 dollars a day in PUT requests alone, before a single byte is stored. Going to 200 ms chunks to fix the chat problem multiplies that by ten and removes the cushion entirely. #### Start here **Why this design** Live streaming is the system with no retry: a lost second is gone, and the only protection is a buffer somebody is already holding. Interviewers use it to see whether you can trace glass-to-glass latency hop by hop, explain why one segment serves a million viewers only while every viewer requests the identical URL, and size a transcode fleet that must run in real time with no queue to hide in. Every live video platform and sports broadcaster tunes the same dial this board is built around: segment length. **What you will learn** - Why segment length sets latency, resilience and cost at once: 2 s segments in a 3-segment window give ~8 s glass-to-glass and exactly three segments of cushion. - How the edge absorbs the audience: 1.5M requests/s at the edge, half of them manifests, against ~5,000 origin fetches/s whatever the audience size. - Why live transcode is sized for the worst minute of the year: 375,000 cores for 50,000 channels x 5 rungs, and a rung at 1.05x real time never catches up. - The cost nobody budgets: 125,000 object PUTs/s, ~$19.7M/year, and ten times that at 200 ms LL-HLS parts. - Why the broadcaster pushes to two ingest POPs, and why deploys drain by channel and never cut a live broadcast. - Why chat at 200 ms spoils the goal, and the two honest fixes. **How to read this board** - Press play once: the walkthrough follows one broadcast from the camera to the eye, then to the chat and the money. - CONTRIBUTION - THE FIRST TWO SECONDS, ALREADY SPENT (top left): the encoder, the keyframe interval and the redundant push. - INGEST AND REAL-TIME TRANSCODE, then PACKAGE - SEGMENT LENGTH IS THE DIAL: the top row, left to right. - DELIVERY - AND THE TRIP BACK TO A HUMAN: the second row, right to left - shield, edge, player, viewer, chat. The distance around that loop is the latency. - THE RECORDING: the DVR window and the replay, kept off the live path. - DATA MODEL - WHAT GENERATE SQL READS: segments explains the money; channels.latency_mode is the rollback dial. - Then pages 1 to 6 in order: requirements, capacity, storage, availability, how it works, deployment. #### 1 · Requirements Live is not video-on-demand with a shorter deadline. It is a different system, because the thing every other design leans on - the ability to fetch a byte again - does not exist. A second that is lost is gone. **Functional** - A broadcaster pushes one stream over RTMP or SRT and it is playable worldwide within seconds. - The stream is transcoded to a ladder in real time, so viewers on poor connections are not excluded from a live event. - A viewer joining halfway in starts at the live edge, not at the start. - Chat and stream events are delivered alongside the video. - The broadcast is recorded: a rolling DVR window during the event, and a permanent replay afterwards. - A broadcaster can see, live, whether their own upstream is the problem. **Non-functional** - Glass-to-glass latency target of 8 seconds in standard mode, 3 seconds in low-latency mode. It is a target, not a guarantee, and the difference is the point of this board. - Rebuffer ratio under 0.5 percent - a looser target than video-on-demand, because there is no buffer to hide behind. - Ingest availability 99.99 percent during a broadcast. A dropped ingest is not a degraded experience, it is the event ending. - One segment must serve a million viewers. Any per-viewer element in a manifest or URL breaks that and is treated as an outage, not a feature. - No single-segment loss may stall playback: a missing rung falls back to a lower one within one segment. **Questions to ask before designing this** - What is the latency REQUIREMENT, in the product sense? Betting and auctions need under 2 seconds. A concert needs 10 and would rather never stutter. These are two different systems and the answer changes every box on the diagram. - How many concurrent channels, and how peaked? 50,000 channels at 4 hours each is a fleet-sizing problem; one channel with 20 million viewers is a fan-out problem. They are not the same build. - Is chat in scope? If yes, the spoiler problem is in scope, and either chat must be delayed to match video or video must be dragged down to match chat. - Is DVR required during the broadcast, or only a replay afterwards? Seek-during-live turns a rolling window of three segments into a full manifest of thousands and changes the cache behaviour completely. - What upstream do broadcasters actually have? Designing for 6 Mbps when the median is 2 means the ladder starts below where you thought. - Who pays for the egress - the platform or the broadcaster? That answer decides whether the top rung exists. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 11 steps; a schema of 5 tables and 31 columns. ### Streaming playback - the bytes were already next to you - URL: https://tierslate.com/templates/netflix-streaming - Kind: system architecture board; level: deep - Access: paid, 800 AI credits - the question is public, the worked answer is not Why streaming is a bandwidth problem, solved by placing the bytes inside the network before anyone asks. What is hard about it, left in on purpose: Content is pre-positioned before anyone asks for it, so every edge appliance holds 200 TB of a 8 PB catalogue - 2.5 percent - chosen by a popularity model that is wrong for a whole country the day something unexpected trends, and every wrong guess is bandwidth paid for content nobody watched plus an origin fetch for the content they did. And the second-by-second decision that actually determines whether playback looks good is made by the CLIENT, from a throughput estimate that is always a few seconds stale: the player switches down after the buffer has already drained, which is why a rebuffer is usually visible in the telemetry a full ten seconds before the viewer sees the spinner. #### Start here **Why this design** "Design Netflix" is asked constantly and answered badly, because people draw a video database and a CDN box and stop. The real system is two systems with a 450:1 traffic ratio between them: a control plane that decides once per session what you may watch and from where, and a data plane that moves 198 PB a day in six-second segments. Interviewers use it to see whether you notice that the hard problem is bandwidth rather than storage, and whether you know who actually decides the bitrate - the client. Streaming teams spend their money on exactly those two facts. **What you will learn** - Why the control plane (about 8,000 requests a second at peak) and the data plane (3.5 million segment GETs a second) are separate bands that scale and fail differently. - How to size by bandwidth: 15 million concurrent streams at 3.5 Mbps is 52.5 Tbps of egress, and why that makes a commercial CDN a $4M-a-day line item. - What a manifest carries - licensing, the bitrate ladder this device may use, and which edge box to fetch from - and why it must be correct even though it moves almost no bytes. - How adaptive bitrate works from the client side, and why its throughput estimate is always a few seconds stale. - Why content is pre-positioned inside ISPs before anyone asks for it: each appliance holds about 2.5% of an 8 PB catalogue, chosen overnight by a popularity guess. **How to read this board** - Press Play and follow the walkthrough: CONTROL PLANE across the top (once per session), then DATA PLANE underneath it (450 times per session). - Then BEFORE ANYBODY ASKS on the right - the overnight fill that puts the bytes next to the viewer - and QoE along the bottom, the only feedback loop. - 1 · Requirements - what must be correct and what may merely be fast. - 2 · Capacity estimation and 3 · Storage estimation - why 8 PB is small and 198 PB a day is not. - 4 · Availability - control plane down stops new sessions, data plane down stops the world. - 5 · How it works, then 6 · Deployment plan. - Finally the DATA MODEL below the band, then the argument this board leaves open on purpose: the pre-positioning guess is wrong for a whole country the day something unexpected trends, and the client switches bitrate after the buffer has already drained. #### 1 · Requirements Playback is judged on three numbers a viewer can feel: how long from pressing play to the first frame, how often it stops, and whether it looks like the television it is being watched on. Everything here is in service of those three. **Functional** - A viewer presses play and gets a first frame in under two seconds on a broadband connection. - Playback adapts to the network continuously, without stopping, across the whole bitrate ladder. - Only titles licensed in the viewer country and period are playable, and a device is only offered codecs it can actually decode. - A DRM licence is issued per session per device and is revocable. - Resume where you stopped, on a different device, within seconds of stopping on the first. - Content is positioned close to viewers before they request it, and the positioning decision is measurable after the fact. **Non-functional** - Start-up latency p95 under 2 seconds; rebuffer ratio under 0.2 percent of playing time across the fleet. - Playback API availability 99.99 percent. It runs once per session, so an outage here stops new sessions - people already watching do not notice for 45 minutes. - Data plane availability is effectively 100 percent by construction: many edges, and a client that fails over to another one mid-stream without a visible pause. - Cost per gigabyte delivered is a first-class metric, tracked per ISP, because it is the difference between a viable business and an unviable one. - No licence, key or manifest may be cacheable at a shared cache. The bytes are shared; the permission to play them never is. **Questions to ask before designing this** - What is the concurrency peak, not the DAU? Every number that matters on this board - bandwidth, appliance sizing, API capacity - falls out of concurrent streams, and the ratio of peak concurrency to DAU varies by a factor of three between countries. - Do we own CDN capacity or rent it? This single answer changes the architecture more than anything else on the page, because it decides whether pre-positioning is free or billed. - How big is the catalogue in hours, and how many encodes per hour? 8 PB against 80 PB is the difference between an appliance holding 2.5 percent of the library and holding a rounding error. - What fraction of viewing is the head? If the top 500 titles are 80 percent of hours, pre-positioning works. If the tail is fat, most of this design is wasted money. - Is offline download in scope? It changes DRM from a session-lifetime problem to a persistent-key problem, which is a different security posture entirely. - What device classes must be supported? A ten-year-old television that only decodes H.264 at 1080p forces every title to keep an H.264 ladder alive forever, and that is a permanent line on the storage bill. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 11 steps; a schema of 5 tables and 32 columns. ### Transcoding pipeline - 480 tasks, one file - URL: https://tierslate.com/templates/video-transcoding - Kind: system architecture board; level: core - Access: paid, 400 AI credits - the question is public, the worked answer is not Why one number, the size of a segment, sets the cost, latency and failure behaviour of the whole pipeline. What is hard about it, left in on purpose: Splitting a job into 480 six-second tasks makes a failure cost 0.2 percent of the work instead of all of it, and it does that by turning one job into 480 rows of state, 480 leases and 479 joins that must not click. The job is then only as fast as its slowest task, so the p99 of a task is the p50 of a job, and one poisoned segment that fails three times holds up a stitch that 479 completed tasks are already paid for and waiting on. On top of that the whole fleet runs on spot capacity at a third of the price, which means about 5 percent of tasks an hour are killed mid-encode by the cloud provider rather than by anything in this design - a rate that is fine at six-second tasks and catastrophic at four-hour ones, so the small unit is paying for the cheap machines and the cheap machines are why the small unit was necessary. #### Start here **Why this design** A transcoding pipeline is the cleanest example of a batch system whose cost, latency and failure behaviour are all set by one number: the size of the unit of work. Interviewers use it to see whether you can reason about fan-out, leases versus locks, stragglers and idempotent retries without a user in the loop to hide behind. Every video platform, every media archive and every ML preprocessing fleet runs some version of it, and almost all of them run it on preemptible machines. **What you will learn** - Why a six second, keyframe-aligned segment turns one 8 minute job into 480 tasks, and what that buys: a failure costs 0.23 core-minutes instead of 112. - Why the scheduler hands out a LEASE with a 30 second heartbeat rather than a lock, and why that one choice is what makes 70 percent spot capacity usable - about $37,000 a day saved. - How a job is only as fast as its slowest task, so task p99 becomes job p50, and how fair share stops a 14,400 task backfill from starving a 90 second upload. - Why three retries then a poison queue read by a PERSON, and why every failed attempt is kept after the task finally succeeds. - Why the stitch is a container copy with the encoder version pinned per rendition, and why exit code 0 is not a quality check (VMAF plus a black-frame scan). - The hidden bill of the small unit: 16,700 state writes a second and 2.24 PB of scratch that exists only because the work was cut up. **How to read this board** - Diagram first, and it snakes. Along the top, left to right: SUBMIT - WHAT A JOB ACTUALLY IS (job API, ladder chooser, splitter, scratch), then STATE - ONE ROW PER SEGMENT. - Back right to left through DISPATCH - WHO WAITS, AND FOR HOW LONG (task queue, the red fair-share box, the scheduler hex) into WORKERS - THE ONLY LINE ON THE BILL (CPU and GPU pools). - Then along the bottom: WHEN A SEGMENT FAILS (retry policy, poison queue - note the long dashed wire back to the queue), and FINISH - STITCH, CHECK, AND THE BILL. Press play for the twelve step walkthrough. - Then the pages in order: 1 · Requirements, 2 · Capacity estimation (the queue-sizing deficit is the fleet argument), 3 · Storage estimation (what a failed segment costs, in numbers), 4 · Availability, 5 · How it works, 6 · Deployment plan. - Finally DATA MODEL - WHAT GENERATE SQL READS, below the pages: tasks is the table that is the architecture. - The conversation to have: six seconds is the bill. Small tasks make spot capacity survivable and failures cheap, and in exchange you pay 480 rows of state per job, a straggler-bound job time, and a poisoned segment that holds 479 finished tasks hostage. Argue the segment length before anything else. #### 1 · Requirements A pipeline that turns one source file into a set of renditions, on machines that can be taken away at any moment, without ever producing a file that is quietly wrong. **Functional** - Accept a job naming a source and a target ladder, and return an id immediately. - Split the source into independently decodable segments and encode them in parallel. - Produce every rung of the ladder, in the codecs required, and stitch each one back into a single continuous rendition. - Retry a failed segment without redoing the job, and stop retrying when the failure is clearly not transient. - Report per-job progress that is honest - derived from completed tasks, not estimated from elapsed time. - Verify output quality before publishing: a VMAF score per rendition and a scan for black or frozen frames. - Account for what each job cost, in core-minutes, per rung. **Non-functional** - Throughput: 480 million tasks a day, 5,555 a second average and three times that at peak. - A job of 8 minutes completes in under 10 minutes at p50 and under 45 at p99. The gap between those two is almost entirely the slowest task in each job. - No task is executed twice in a way that can corrupt output. At-least-once delivery plus idempotent, content-addressed writes, never exactly-once as a promise. - Correctness over throughput at every choice: a wrong rendition that is published is far worse than a slow one that is not. - The fleet runs at 70 percent or more on preemptible capacity, and losing a machine mid-task is a routine event with no operator involvement. **Questions to ask before designing this** - What is the segment length, and who decided it? It sets the retry cost, the row count, the parallelism ceiling and the number of joins that can click. It is the single most consequential number here and it is usually inherited from a tutorial. - Is the ladder fixed or per-title? Per-title encoding saves 20 to 30 percent of compute and adds an analysis pass that delays every job start. - Are GPUs allowed? They are 20 times faster and worse at the same bitrate, so the answer differs per rung, and using them for archival masters is a decision you cannot undo without re-encoding the catalogue. - What is the acceptable cost of a failed job - a retry, or a phone call? That decides whether the poison queue has an automatic consumer or a human one. - How much does a job know about its deadline? Interactive uploads and catalogue backfills in one queue with no priority means the backfill wins by being first, which is exactly backwards. - Is audio segmented too? Segmenting audio on video keyframe boundaries is how you get an audible click at every join, and it is found late. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 12 steps; a schema of 5 tables and 35 columns. ### Video upload - one file in, seven encodes out - URL: https://tierslate.com/templates/youtube-upload - Kind: system architecture board; level: core - Access: paid, 400 AI credits - the question is public, the worked answer is not Why a durable receipt for a 500 MB upload and a video that plays everywhere are two separate promises. What is hard about it, left in on purpose: Every upload gets the full ladder eagerly, and the 500 MB master is kept forever on top of it. Most videos on a service like this are watched fewer than ten times, so the 1080p AV1 rung - which costs 7.4 of the 14 core-minutes each source minute burns, more than every other rung combined - is usually encoded for nobody. The honest alternative is to encode 360p now and the rest on first demand, which cuts the compute bill by more than half and makes the first viewer of a cold video wait. Nothing on this board can tell those two videos apart at upload time, because the only signal that separates them - whether anyone watches - arrives after the money is already spent. #### Start here **Why this design** Video upload is the interview question about promises that pull in opposite directions: an immediate, durable receipt for a 500 MB file over a flaky connection, and a video that eventually plays on every device. It probes whether you can split a system at the point where the client goes away, make an upload resumable rather than merely retryable, and price an asynchronous pipeline whose cost lands before the video has earned a single view. Every product that accepts large user files - video, podcasts, design assets, backups - faces the same split. **What you will learn** - How a resumable session works: 100 chunks of 5 MiB, each a PUT with Content-Range, and the server - never the client - answering "resume from byte N". - Why the creator is told "done" when the master is durable, not when anything plays, and why "Processing" is the gap between two writers of one state field. - Why the source is cut on keyframes into ~6 second segments, so 200 machines encode one film in parallel. - Where the money goes: 14 core-minutes per source minute, 7.4 of them on the 1080p AV1 rung - $13.6M a year of compute before anyone presses play. - Why the renditions (624 MB) are bigger than the master (500 MB), and why CMAF packaged once serves both DASH and HLS from the same bytes. **How to read this board** - Diagram first, top band: WHILE THE CREATOR IS WATCHING - creator, uploader, upload edge, the upload API hex and its session store. It ends at the master store, because that is where the client is told "done". - Then DURABLE - AND ON THE BILL on the right: the master store and the video record that both halves write. - Then below the fold, AFTER THE CONNECTION CLOSED - WHERE THE MINUTES GO: the job queue, segmenter, rendition fan-out and its three rung boxes, plus the frame sampler; then PACKAGE AND PUBLISH. Press play for the eleven step walkthrough. - Then the pages in order: 1 · Requirements, 2 · Capacity estimation (the transcode fleet is the real capacity question), 3 · Storage estimation, 4 · Availability, 5 · How it works, 6 · Deployment plan. - Finally DATA MODEL - WHAT GENERATE SQL READS, below the pages: one row per video, per rendition, per job attempt, and a session row deleted once the master is durable. - The conversation to have: the whole ladder is encoded eagerly for every upload and the master is kept forever, while most videos are watched fewer than ten times. Encoding 360p now and the rest on first demand halves the compute bill and makes the first viewer of a cold video wait - argue that trade with the numbers on pages 2 and 3. #### 1 · Requirements A creator uploads one file. The service owes them two things that pull in opposite directions: an immediate, durable receipt, and a video that eventually plays everywhere. Everything below follows from separating those two promises. **Functional** - Upload one video file of up to 256 GB and receive a durable receipt before any encoding has started. - Survive a lost connection: an interrupted upload resumes from the last byte the server acknowledged, not from zero. - Transcode every accepted upload into a ladder of renditions and package it for both DASH and HLS. - Extract a poster frame and a scrub-bar storyboard sprite from the source. - Expose the state of a video - uploading, processing, ready, failed - and the reason for a failure. - Retain the master so the whole catalogue can be re-encoded when a better codec arrives. **Non-functional** - Upload API availability 99.9 percent monthly. A chunk PUT must be retryable without corrupting the session. - Time to first playable rendition: p50 under 90 seconds, p95 under 8 minutes. Full ladder p95 under 30 minutes. - Master durability 11 nines. A lost master is content loss that no backup of the database can undo. - No accepted byte is ever silently dropped: an upload either becomes a master object or is reported missing. - Cost per uploaded minute is a tracked, alarmed figure, because it is the dominant variable cost of the product. **Questions to ask before designing this** - What is the size DISTRIBUTION of uploads, not the average? The mean drives storage, the p99 drives chunking and timeouts, and on a video product they are an order of magnitude apart. - Is the ladder fixed or per-title? Per-title encoding moves the compute bill by 20 to 30 percent and adds a complexity-analysis pass before any encode starts. - How long must a resumable session stay resumable? Seven days of resumability is seven days of half-uploaded objects you are paying to store and may never complete. - Do we also have to ingest live? If yes, the segmenter and packager are shared with a system that cannot buffer, and that constraint reaches back into this board. - What happens to a master after five years - archive tier, or delete it and keep the top rendition as the new source? - Is the top rung a paid feature? If it has revenue attached, the eager-versus-lazy encoding argument has a different answer. - Who is allowed to delete a video, and does delete mean the renditions, the master, or both? The answer decides whether the storage curve can ever go down. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 11 steps; a schema of 5 tables and 34 columns. ### File upload - the bytes never touch your server - URL: https://tierslate.com/templates/file-upload - Kind: system architecture board; level: starter - Access: paid, 200 AI credits - the question is public, the worked answer is not How presigned URLs send uploads straight to object storage, and what the server still has to check afterwards. What is hard about it, left in on purpose: The presigned URL is a plain PUT with only the object key and Content-Type signed, so the storage does not enforce the size the client declared - a URL issued for a 2 MB avatar accepts a 5 GB upload, and you pay to store it until the worker notices. The content type is whatever the client claimed until the worker reads the file's magic bytes. Verification is driven by bucket events, which are at-least-once and can arrive seconds late, so the worker must be idempotent and the user sees "processing" for an unknown time. And an upload that is started and never finished leaves a pending row and, for multipart uploads, invisible parts that are billed forever - nothing on this board cleans them up except one lifecycle rule. #### Start here **Why this design** Uploading a file sounds like a form field, and it is one of the most common places a design goes wrong at scale: app servers sized for bandwidth, workers tied up by slow phones, files served before anyone checked what they are. Interviewers ask it to see whether you know the presigned URL pattern, and - the better half of the answer - what that pattern stops the server from checking. The same design is behind profile photos, document uploads, chat attachments and video ingestion. **What you will learn** - Why 2 million 2.5 MB uploads a day is ~1.4 Gbit/s at peak, and why that traffic should not pass through your app servers. - What a presigned URL is: a signed, expiring permission for one method on one object key. - Why the upload lands in a private "incoming" area and is only copied to the served bucket after it passes checks. - What the server must still check afterwards: real size, real type (magic bytes), malware, image dimensions, metadata like GPS in EXIF. - How to clean up uploads that never finish, and why multipart uploads are invisible until you abort them. **How to read this board** - GET A URL across the top: user, client app, the upload API hex. - Then the long wire from the client straight to the red INCOMING bucket - the bytes going around your servers. - CHECK IT AFTERWARDS across the middle: the bucket event queue, the verify worker hex and the malware scanner. - SERVE across the bottom: the viewer and the CDN, which can only reach the checked bucket. - STORAGE on the right: upload rows, incoming, files, and the lifecycle rule that cleans up. - Press play, read pages 1 to 6, then the DATA MODEL. The conversation to have: list every check the server can no longer make on the way in, and where on this board each one moved. #### 1 · Requirements **Functional** - A signed-in user uploads images (JPEG, PNG, WebP up to 10 MB) and documents (PDF up to 50 MB). - Show upload progress, and resume a large upload after a dropped connection. - After upload the file is checked, images get thumbnails (256 px and 1024 px), and the file becomes viewable by its link. - Reject files that are the wrong type, too big, or malicious - and tell the user. - The owner can delete a file; it stops being served. **Non-functional** - App servers do not carry file bytes. Their capacity is sized for requests, not bandwidth. - No file is served to anyone until it has passed the checks. - An uploaded file is durable once the user sees "uploaded" (object storage: 11 nines). - Processing finishes within 30 seconds for 99 percent of images. - Storage costs are bounded: abandoned and rejected uploads are deleted within a day. **Out of scope** - Video transcoding - the same front half, with a much bigger pipeline after it. - Collaborative editing of documents. - End-to-end encryption, where the server cannot check content at all. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps; a schema of 3 tables and 23 columns. ### Music streaming - three minutes of audio, played a billion times - URL: https://tierslate.com/templates/music-streaming - Kind: system architecture board; level: core - Access: paid, 400 AI credits - the question is public, the worked answer is not Why audio is small enough to prefetch, and why play counts and royalties are the hard ledger underneath. What is hard about it, left in on purpose: The money is computed from data that is neither complete nor trustworthy when the month closes. The 30-second rule is enforced by the client, which also batches reports and may be offline for up to 30 days, so the royalty run on the 1st either waits (and pays everyone late) or closes on an incomplete ledger and restates next month - this board closes and restates, which means every statement is provisional. The fraud filter sits between the qualifier and the ledger, but the public play counter is fed from the qualifier, before fraud is removed, so a botted track shows a million plays on its page and a fraction of that on its statement. And because the pool is pro-rata, a fraud farm that slips through does not cost us anything: it costs every honest artist in the territory a slice of their share, and nothing on this board tells them. #### Start here **Why this design** Music streaming looks like video streaming with smaller files, and the smaller files are exactly why it is a different problem. A track is about 4 MB, so delivery collapses into "put it on a CDN and prefetch the next one", and the interesting system is the one underneath: every play is a ledger entry in a monthly pro-rata royalty split, reported by a client you do not control, sometimes weeks late. Interviewers use it to see whether you notice that the hard part moved. **What you will learn** - Why a 4.2 MB track can be prefetched whole, so gapless playback and instant skips are a client decision, not an adaptive-bitrate one. - Why playback starts with a licence check by territory, not with a byte. - Why a play only counts at 30 seconds, and why the client is the one that measures it. - Why the public play counter and the royalty ledger are different systems with different numbers. - Why play events are deduplicated by a client-minted play_id, because offline phones retry for days. - Why pro-rata royalties make stream fraud everyone's loss, not the platform's. **How to read this board** - Press play once: the walkthrough follows one play from the button to a rights holder's statement. - PLAY: licence, signed URL, CDN, prefetch. - COUNT: the client's report, the 30-second rule, the fraud filter and the ledger. - DATA MODEL: the ledger as a table, and the royalty lines it closes into. - Then pages 1 to 6 in order: requirements, capacity, storage, availability, how it works, deployment. #### 1 · Requirements Two products share one button. The listener wants music now; the rights holder wants an accurate count of how many times it played. **Ask these before designing** - How is a royalty computed? Pro-rata (a share of the whole pool) or user-centric (each subscriber's fee split among what they played)? Pro-rata is the industry default and is what makes fraud a zero-sum theft between artists. - What counts as a play? The common rule is 30 seconds of audio actually heard. Skips under 30 s are product signals, not royalty events. - Is offline listening supported? If yes, plays arrive late, and the ledger needs a late-arrival window and a restatement policy. - Territories: a track can be licensed in Germany and not in Japan. Is the catalogue per country? (It is.) - Free tier with ads? Changes the pool, not the architecture. **Functional** - Search and browse a catalogue of 100M tracks. - Play a track within 250 ms of pressing play; next track plays gaplessly. - Download for offline play, with licence expiry. - Record qualifying plays exactly once, per listener, per territory. - Show a public play count per track. - Close a monthly royalty statement per rights holder. **Non-functional** - Time to first audio p95 under 250 ms on a warm client, 600 ms cold. - No double-counted plays. A retried batch must not inflate a statement. - Late plays accepted for 30 days, restated into the next statement. - Playback availability 99.95%; the ledger may lag by hours but must never lose an event. - Public counter may be up to an hour stale and is explicitly approximate. **Out of scope** Podcasts and video, recommendation models, the ad server, lyrics, social features, label onboarding contracts. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps; a schema of 5 tables and 35 columns. ### Short video feed - the next swipe is already downloading - URL: https://tierslate.com/templates/short-video-feed - Kind: system architecture board; level: deep - Access: paid, 600 AI credits - the question is public, the worked answer is not Why a short-video feed is ranking plus prefetch, and why the first second of video decides retention and cost. What is hard about it, left in on purpose: Ranking live on every request costs about 40 million model scores a second at peak, and the board pays it because a precomputed feed is stale after one swipe - yet prefetch means the next three videos were chosen BEFORE the viewer reacted to the current one, so the freshest ranking in the system is already three videos out of date when it plays. Prefetch depth is a direct trade of start-up time against wasted bandwidth: two seconds of three videos ahead wastes about 2 percent of egress, prefetching whole videos would waste 15 to 20 percent, because a third of videos are swiped away inside two seconds. The 5 percent explore budget that gives new uploads a chance is watch time deliberately spent on worse videos, and the model only ever learns from what it chose to show, so its blind spots never correct themselves. And the long tail misses the CDN about 40 percent of the time, so the first second of an obscure video - exactly the one explore is trying to promote - is the slowest one on the platform. #### Start here **Why this design** "Design TikTok" is often answered as "design YouTube, but shorter", and that answer misses both hard parts. A short-video feed has no search box and no subscriptions page to fall back on: the ranked feed IS the product, it is recomputed with every swipe, and it has to start each video in under 200 ms because a new one starts every 15 seconds. The question tests whether you can design ranking and delivery as one system, and whether you can put numbers on the bandwidth you waste to make swipes feel instant. **What you will learn** - Candidate generation versus ranking: why you retrieve about 1,500 items cheaply from several sources and score only about 500 expensively. - Why the feed is ranked per request rather than precomputed, and what that costs at 80,000 requests a second. - Prefetch: how many videos ahead, how many seconds of each, and what each choice costs in wasted egress. - Why the first segment of every video is encoded and cached differently from the rest. - The learning loop: watch time as the label, the explore budget for new uploads, and the feedback loop where a model only learns from what it showed. **How to read this board** - Press Play. THE PHONE asks for a page, RANKING builds it from candidates, scores it and re-ranks it for diversity. - The page returns and the player prefetches the start of the next three videos from DELIVERY. - Watch events flow into LEARNING, which updates features within seconds and the model every hour. - Then pages 1 to 6 and the DATA MODEL. - Argue with what the board leaves in: prefetched videos ranked before the viewer reacted, the explore budget, and the cold tail that misses the CDN. #### 1 · Requirements **Ask these before drawing anything** - What is the objective? Total watch time, completions, shares and "not interested" are different labels and produce different feeds. Most real systems combine several into one weighted score; say which. - How fresh must ranking be? Per request (reacts to the last swipe) or per session (computed once)? This board says per request. - What start-up time is acceptable? Under 200 ms from swipe to first frame is the bar for a feed that feels instant. - How much may we spend on bandwidth that nobody watches? It must be a number, because prefetch converts it directly into start-up time. - How much exposure do new uploads get? Without an explicit explore budget, a new creator is never shown and therefore never learned about. - Upload and transcoding are their own design (see the video-transcoding board). Here they are an input: a video arrives already encoded into a ladder. **Functional** - GET a page of 10 ranked videos for this viewer, excluding anything already watched. - The player starts the next video on swipe without a visible spinner on a typical 4G connection. - Every impression and watch event (watched ms, completed, replayed, swiped at ms, liked, shared) is recorded. - New uploads enter the feed within minutes of publishing through an explore pool. - No two consecutive videos from the same creator; at most 3 of 10 from one topic cluster. **Non-functional** - Feed request p99 under 150 ms server side. - Time to first frame p75 under 200 ms for prefetched videos, under 600 ms for a cold start. - Wasted prefetch bytes under 3 % of total egress. - A model or feature store outage degrades the feed (trending fallback); it never shows an empty screen. **Out of scope** - Upload, transcoding and moderation. - Comments and messaging. - Ads insertion (the ad-serving board) - though it takes slots from this same page. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps; a schema of 5 tables and 36 columns. ### Video calls - forwarding streams, not mixing them - URL: https://tierslate.com/templates/video-conferencing - Kind: system architecture board; level: deep - Access: paid, 800 AI credits - the question is public, the worked answer is not Why a group call forwards streams through an SFU, and how bandwidth estimation keeps a bad link from ruining the call. What is hard about it, left in on purpose: The SFU adapts per RECEIVER and can do nothing about a SENDER: if Alice's uplink drops to 1.2 Mbps her encoder sheds the 720p layer and every one of eleven people loses her in high quality, and simulcast itself costs her about 40 percent more upload and encoder CPU than one stream - the exact resource a weak laptop on hotel wifi lacks. Switching a receiver between layers needs a keyframe, and a keyframe is 5 to 10 times the size of a normal frame, so one subscriber on a flapping link that requests keyframes repeatedly makes Alice send bitrate spikes to everyone; the SFU must rate-limit those requests, which means that subscriber waits a second frozen. Downlink grows with the call: an SFU forwards N-1 streams to each person, so a 50-person call is unwatchable without capping the grid and forwarding only the last few active speakers, which is a product decision disguised as a bandwidth one. Each call is pinned to one SFU process for its lifetime, so a node crash drops every call on it for the 2 to 5 seconds it takes clients to reconnect elsewhere, and cascading SFUs across regions to keep latency down adds a hop and a second machine that can fail. And end-to-end encryption, which the forwarding design makes possible because the SFU never decodes, also makes server-side recording, transcription and noise suppression impossible unless a participant's client does them. #### Start here **Why this design** Zoom, Google Meet, Teams, Discord and every WebRTC product built on LiveKit, mediasoup or Janus make the same choice for group calls: a Selective Forwarding Unit in the middle that receives each participant's media once and forwards it, encrypted and undecoded, to everyone else. The interview question "design a video call for up to 50 people" is about knowing why - why not peer to peer, why not mix on the server - and then about the part that decides whether the call is any good: how the system notices that one person's network is bad and gives them less, without giving everyone less. **What you will learn** - Mesh vs MCU vs SFU in numbers: 16.5 Mbps of upload per person, a full transcode per layout, or a packet copy. - Why signalling (who is in the room, SDP) and media (RTP over UDP) are different systems with different failure modes. - ICE, STUN and TURN: why 10 to 15 percent of participants need a relay, and what TURN over TCP 443 costs. - Simulcast: three encodings from one camera, and the 40 percent upload tax it charges the sender. - Bandwidth estimation from transport-wide feedback, and how the SFU picks a layer per subscriber per second. - Keyframes, NACK and PLI - and the keyframe storm one bad receiver can cause. - The sizing: 2 million concurrent participants, 5 Tbps of SFU egress, about 900 SFU nodes, and a 150 ms mouth-to-ear budget. **How to read this board** - Press play: eight steps follow Alice joining, sending one camera, and Bob and Carol receiving it at different qualities. - JOIN: Alice, signalling and the room state - none of this carries a single video packet. - SEND: the simulcast encoder, the uplink decision, and the TURN relay for networks that block UDP. - FORWARD: the SFU, the per-subscriber layer decision, the bandwidth estimator, keyframe control, the recorder, and the cascade link. - RECEIVE: Bob on fibre, Carol on 3G in red, the jitter buffer, and the remote SFU in another region. - Pages 1 to 6, then the DATA MODEL. #### 1 · Requirements **Ask these before drawing anything** - How big is a call? 2 to 12 is the common case; 50 interactive is the hard case; 1,000 watching is a broadcast, which is a different system (HLS or a fan-out tree of SFUs with view-only participants). - Where are the participants? One region allows one SFU. Global calls need cascading, and the routing decision is per call. - Is end-to-end encryption required? It is compatible with an SFU (it forwards opaque frames) and incompatible with server-side recording, transcription and mixing. - Does the call need recording and live captions? Both are clients of the SFU, and both need to decode, which settles the encryption question for those calls. - What devices? A phone on cellular cannot encode three 720p layers; the client must be able to offer two. - What is the tolerated latency? Conversation breaks down above about 300 ms one way; the target is 150. **Functional** - Create and join a room by link; up to 50 interactive participants. - Camera, microphone and screen share; mute and unmute; active-speaker detection. - Each participant sees a grid or a speaker view; quality adapts per viewer. - Calls survive a participant changing network (wifi to cellular) without leaving. - Optional cloud recording and live captions. **Non-functional** - Mouth-to-ear latency under 150 ms p50 and 300 ms p95 within a region. - Audio is never sacrificed for video: under congestion video degrades first, to a frozen tile if necessary. - Join-to-first-frame under 2 s. - 2 million concurrent participants at peak; 10 million meetings a day. - An SFU node failure reconnects its calls within 5 s, automatically. **Out of scope** - Telephone dial-in (a SIP gateway is its own component). - Large webinars over 50 interactive seats. - Server-side noise suppression in end-to-end-encrypted calls - by construction the server never sees audio. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps; a schema of 6 tables and 41 columns. ## Marketplaces & booking ### Checkout - holding stock you have not sold yet - URL: https://tierslate.com/templates/amazon-checkout - Kind: system architecture board; level: core - Access: paid, 400 AI credits - the question is public, the worked answer is not Why stock is held before the card is charged, and what must unwind when the charge fails. What is hard about it, left in on purpose: The reservation is held in one store and the charge happens in another, so there is no transaction across them - only a compensating message. That message is the weak point and it is drawn as a queue with no dead-letter: if the release worker crashes mid-batch, or the compensation event is dropped, the stock stays held with nothing left to release it, and the only thing that ever recovers it is the TTL sweeper running on a clock nobody alerts on. The sweeper is a correctness hazard in the other direction too - it will happily expire a hold belonging to a payment still in a 3-D Secure redirect, so a shopper who authenticates slowly gets charged for a unit that was given away while they were typing a code. The idempotency store makes a retried POST safe and does nothing at all about a retried capture at the processor. #### Start here **Why this design** Checkout is the classic interview question for a distributed transaction that cannot be a transaction: the stock lives in a database you own and the money moves through a card network you do not, and nothing commits both at once. Interviewers use it to see whether you reach for a saga with explicit compensation, idempotency at two different layers, and a database constraint rather than a service boundary to stop the double sell. Real commerce teams live it every peak day, when a 0.1% bug becomes thousands of oversold orders and refunds. **What you will learn** - Why the order of operations is hold, then charge, then commit, and what each step owes the hold if a later step fails. - How UNIQUE (order_id, variant_id) and CHECK (reserved <= on_hand) make overselling impossible no matter which service is confused. - Why an Idempotency-Key on POST /orders stops a second order but not a second capture, and why the processor needs its own key. - How to size the contended working set: about 1.95M live holds, 293 MB at a 700 orders/s peak - a contention problem, not a volume one. - Why the TTL sweeper is both the only backstop for a lost compensation and a hazard for a shopper still in a 3-D Secure redirect. - What degrades rather than dies when payment, the reservation store or the event stream is down. **How to read this board** - Diagram, along the happy path: CART (nothing is promised yet), then PLACE ORDER (the idempotency key and the hold on the red Reservations store), then PAYMENT (the card network, the part that can say no), then COMMIT (the order exists). - Then the band underneath, UNWIND, read right to left: the compensation queue, the release worker and the TTL sweeper - the three ways stock comes back. Press play for the eight-step walkthrough; the last three steps are the failure. - The DATA MODEL under the diagram: five tables, and the two constraints on stock_reservations and inventory that are the whole safety argument. - Page 1 · Requirements - the questions that decide whether reservations exist at all, and the correctness and latency targets. - Pages 2 and 3 · Capacity and Storage - 3M orders/day, 700 orders/s at peak, 25 TB in five years, and the 293 MB that actually matters. - Page 4 · Availability - single points of failure by name, and what degrades instead of failing. - Page 5 · How it works - the request told end to end, including the moment correctness becomes a background job. - Page 6 · Deployment plan - single-writer regions, migrating the dangerous pair of tables, and the one thing that cannot roll back. #### 1 · Requirements Checkout is the twenty seconds between a cart and an order. Everything before it is browsing and everything after it is logistics; this is the only part where the system makes a promise it can be sued over. **Ask these before designing** - Is overselling ever acceptable? For a marketplace of one-off used goods, never. For a retailer with 400 units in four warehouses, a 0.1% oversell that is fixed by a back-order is cheaper than the contention a strict hold costs. The answer changes whether reservations exist at all. - Authorise at order, or capture at order? Authorising holds the funds and captures at ship; capturing takes the money now. Card scheme rules give an authorisation about 7 days, so an item that ships in 3 weeks cannot be authorised at checkout, and the saga grows a re-authorisation step. - How long is a hold? 15 minutes covers a card form. It does not cover a bank redirect on a bad mobile connection, and it does not cover "pay by bank transfer". - One warehouse or many? Availability is per fulfilment centre. A single global count is a lie the moment you have two. - Digital goods in the same cart? They have infinite stock and no hold, which means a mixed cart has two different commit rules. **Functional** - Place an order from a cart: price it, hold the stock, take payment, commit. - A retried request must not place a second order, and must not take a second payment. - Stock held by a failed or abandoned checkout must come back, without a human. - Cancel before dispatch: release stock and refund. - Every order line records what was paid, not what the catalogue says today. **Non-functional** - Correctness beats availability on this path. Refusing an order is a bad day. Selling one unit twice is an apology, a refund, and a support cost of roughly 40x the item margin. - p99 under 2.5 s for POST /orders end to end, of which the card network is 1.2 s and is not ours. - 99.95% for checkout, against 99.99% for browse - checkout is allowed to be the less available half because it is the half that must be right. - Peak is 20x average and it is a calendar event, not a surprise. Capacity is provisioned for the 20x, not autoscaled into it. - Hold release must complete within 60 s of a failure at p99, because a held unit is unsellable inventory. **Explicitly out of scope** Catalogue and search, pricing and promotions, tax calculation, fraud scoring, returns. Each is a real service; none changes the shape of the reservation problem. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps; a schema of 5 tables and 33 columns. ### Auction bidding - the clock is part of the system - URL: https://tierslate.com/templates/auction-bidding - Kind: system architecture board; level: deep - Access: paid, 800 AI credits - the question is public, the worked answer is not Why bids are ordered by a sequence under one lock rather than a clock, and how soft close moves the end. What is hard about it, left in on purpose: Soft close is the fix for sniping and it is drawn here as one, but it is honestly unresolved in two directions. It makes the end time unbounded: two determined bidders can extend a lot for hours, the close job is re-armed every time, and a seller who advertised a 20:00 finish has no contractual end. And it only works if the losing bidder learns they were outbid in time to respond, which means the extension window has to be longer than the outbid notification path - a queue, an email provider and a human reading a phone. That path is minutes at p99 and the window is two minutes, so for most bidders the extension changes nothing and only the programs benefit, which is the opposite of what soft close is for. Underneath both sits the single-writer lot row: every bid on a contested lot in the last ten seconds serialises behind one SELECT ... FOR UPDATE, and there is no way to shard a row that is, definitionally, the thing everybody is fighting over. #### Start here **Why this design** Auction bidding is the system-design question where the load is tiny and the problem is hard: a few bids a second, all on one row, all in the last ten seconds. Interviewers use it to probe whether you can produce a total order without trusting wall clocks, whether you notice that proxy bidding means the stored state is not the price, and whether you treat the close time as state rather than a timestamp. Real marketplaces get it wrong in ways that end up as disputes and refunded sales, not as error rates. **What you will learn** - Why bids on a lot are ordered by a monotonic sequence assigned under one lock, never by timestamps, and how UNIQUE (lot_id, seq) makes the history evidential. - How proxy bidding works: the lot row stores the visible price and the hidden high maximum, and every bid is resolved against the second. - Why the design point is about 4 bids/s on one unshardable row with a 300 ms p99, not the 289 bids/s fleet average. - Why scheduled closes live in a table with (state, fire_at) and locked_until rather than in-memory timers, and why a close and its job commit together. - How soft close turns the clock into state that bids move - and why it only helps people if the outbid notification beats the two-minute window. - Why bidding fails closed, and why a wrongly closed auction is fixed forward rather than rolled back. **How to read this board** - Diagram, top row: THE BIDDERS (one of them is a program), then ONE LOT, ONE LOCK - the gateway, the bid service, the red Lot row, the proxy resolver and the append-only bid history. - Diagram, bottom row: THE CLOCK IS A COMPONENT, NOT A DATE (soft close, close jobs, the close worker), then EVERYONE ELSE, MILLISECONDS LATE (outbid events, notifications, the live price push). Press play for the eight-step walkthrough. - The DATA MODEL under the diagram: bids.seq is the order, lots.high_max_minor is the secret, and lot_close_jobs keyed by lot makes re-arming idempotent. - Page 1 · Requirements - the questions about ordering and time to ask first, and the targets. - Pages 2 and 3 · Capacity and Storage - 25M bids/day, the 4 bids/s on one row that designs the system, and 21 TB in five years. - Page 4 · Availability - why bidding fails closed, and the one degradation that changes product behaviour. - Page 5 · How it works - a worked proxy-bid example, the sequence, the close job, sniping and soft close. - Page 6 · Deployment plan - one region per lot, one time authority, deploying in the close trough, and the shadow proxy-resolver. #### 1 · Requirements An auction is a sequence, not a set. Almost every requirement below is really a requirement about ordering or about time. **Ask these before designing** - Proxy bidding or literal bidding? Proxy - where a bidder enters a maximum and the system bids up on their behalf - is what customers expect and it means the stored state is not the price. That changes the schema and the whole concurrency story. - Is sniping to be prevented, penalised or accepted? eBay accepts it. Most European auction houses use soft close. A sealed-bid or Vickrey format removes it entirely and is a different product. - Who is the authority on time? If the close is 20:00:00, is that our clock, the seller’s local time, or an NTP-disciplined reference? A dispute over a bid placed at 19:59:59.8 is decided by this answer. - Can a bid be retracted? Legally, sometimes. Retraction in a totally ordered sequence means tombstones rather than deletes. - Is the reserve price visible? An invisible reserve means "not met" has to be shown without leaking the number, which constrains the read model. - What is the increment schedule? Fixed, percentage, or banded by price. It is the core of proxy resolution and it is a business rule that changes. **Functional** - Place a bid with a maximum; resolve it against the current high maximum using the increment schedule. - Maintain a totally ordered, append-only bid history that can settle a dispute. - Notify a bidder when they are outbid. - Close a lot at a defined time, declare a winner, and extend the close when a late bid arrives (soft close). - Push live price changes to everybody watching. **Non-functional** - Bids on one lot are totally ordered, and the order does not depend on which application server received them. - Bid p99 under 300 ms, including the lock wait, at 4 bids/s on a single contested lot. - A bid is never silently lost. If it cannot be ordered, it must be refused with an error the bidder sees - an accepted-then-dropped bid is a legal problem, not a reliability metric. - Close fires within 1 s of the scheduled time, at up to 125 closes/s during the daily close cluster. - Read 99.99%, bid 99.95%, both failing closed on the write side. **Explicitly out of scope** Listing creation and catalogue search, seller payouts, shipping, fraud and shill-bidding detection (which is a real and much larger system), dispute resolution workflow. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps; a schema of 5 tables and 32 columns. ### Food delivery - three parties and a promise - URL: https://tierslate.com/templates/food-delivery - Kind: system architecture board; level: core - Access: paid, 400 AI credits - the question is public, the worked answer is not How three parties who can each say no move one order through a shared state machine to a promised ETA. What is hard about it, left in on purpose: The ETA is shown before the restaurant has accepted and before any courier exists, so it is a promise made on behalf of two parties who have not agreed to it - built on a prep-time estimate the restaurant has every incentive to understate and no obligation to meet. Once it is displayed it is a commitment the business is refunded against, so it is stored and never recomputed, which means the customer is shown a number the system already knows is wrong for the fifteen minutes before anybody tells them. The courier side has the matching hole: dispatch optimises each offer greedily, one order at a time, so on a Friday at 19:30 the couriers nearest the busiest restaurants are consumed by whichever orders happened to be placed first, and the orders placed ninety seconds later - to the same restaurant - have no supply left within eight minutes. Nothing on this board is doing global assignment, and the reassignment loop sweetens the fee rather than fixing the allocation. #### Start here **Why this design** Food delivery is the classic three-sided marketplace question: a customer, a restaurant and a courier, none of whom work for you, all moving one order. Interviewers use it to see whether you can keep a shared state machine correct while three independent parties act on it at once, design dispatch for supply that is free to say no, and notice that the dominant load is GPS pings rather than orders. DoorDash, Uber Eats and Deliveroo spend most of their engineering on exactly these seams. **What you will learn** - Why an order is a sequence of compare-and-set transitions, and how that settles a cancel racing an accept. - Why dispatch is an offer with a 30 s expiry and a re-offer loop, not a work queue - and where greedy one-at-a-time assignment breaks at the Friday dinner peak. - Why courier location, 50,000 writes/s and 150x the order rate, is held in memory and never written to the transactional database. - Why the ETA is computed once and stored as a promise, and what that costs when it is wrong. - Why authorise at placement, capture at handoff is the payment shape for an order that can fail three ways. - How sharding by city makes failover, deploys and blast radius one marketplace at a time. **How to read this board** - Press play once: the walkthrough follows one order from checkout to handoff. - CUSTOMER (top left): placing the order, the payment hold and the ETA promise. - THE ORDER (right): the one state machine all three parties write into. - RESTAURANT, then COURIER and WHEN NOBODY ACCEPTS: the two parties who can say no, and the re-offer loop. - DATA MODEL: the tables Generate SQL emits - transitions are append-only, offers are not assignments. - Then pages 1 to 6 in order: requirements, capacity, storage, availability, how it works, deployment. #### 1 · Requirements Three parties, none of whom work for us, and an order that moves between them. Every requirement below is shaped by the fact that two of the three can say no. **Ask these before designing** - Who owns the courier relationship? Employed riders can be assigned. Self-employed riders can only be offered, and the difference rewrites dispatch entirely. - Is the ETA a promise or an estimate? If refunds are issued against it, it is a promise, and it must be stored rather than recomputed. - Can an order be cancelled after the restaurant has started cooking? This is a commercial question - somebody pays for the food - and it decides where CANCELLED sits in the state machine. - Batching: can one courier carry two orders? It roughly halves delivery cost and roughly doubles the complexity of both assignment and the ETA. - How fresh must courier location be? 4 seconds is a smooth map and 50,000 writes/s. 15 seconds is a jerky map and 13,000 writes/s. This single number is the largest line in the infrastructure bill. - What happens when no courier accepts? Sweeten and re-offer, widen the radius, or cancel and refund. All three are real answers and they are product decisions. **Functional** - Place an order, authorise payment, route it to a restaurant. - Restaurant accepts or rejects, and reports a prep time. - Offer the delivery to couriers and let them accept or decline; re-offer on timeout. - Track the order through a legal sequence of states, driven by three different actors. - Show live courier position and an ETA to the customer. - Capture payment at handoff, refund on failure. **Non-functional** - A transition must be legal from the state the order is actually in. Two parties will act on the same order in the same second; exactly one may win. - Order placement p99 under 1.2 s. - Offer round trip under 30 s - a courier staring at a phone for longer declines. - Ordering 99.95%, tracking 99.99% and degrading to text rather than a map. - Demand is two spikes a day, not a curve: 40% of the day’s orders land between 19:00 and 20:30. - Location writes are lossy by design. A dropped ping is a slightly jerkier map, never a wrong order. **Explicitly out of scope** Menu management, pricing and promotions, courier payouts and tax, restaurant onboarding, fraud, customer support tooling. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps; a schema of 5 tables and 38 columns. ### Hotel booking - selling a range, on purpose, slightly twice - URL: https://tierslate.com/templates/hotel-booking - Kind: system architecture board; level: core - Access: paid, 400 AI credits - the question is public, the worked answer is not Why a stay is sold night by night or not at all, and why selling the last room twice is sometimes deliberate. What is hard about it, left in on purpose: Overbooking is a priced business decision and the board draws it as a component, which is the honest version - but the percentage is computed from historical no-show rates, and the third party pushing the most inventory changes into this system is a channel manager whose availability view is seconds to minutes behind ours. So the overbooking margin is stacked on top of a synchronisation lag of unknown size, and the two failure modes are indistinguishable from the outside: a night that oversells because the policy said 103% and a night that oversells because Expedia sold a room we had already sold both end with a guest in the lobby at 23:00. Nothing on this board can tell the operator which one happened. The cancellation window is the second unresolved edge: free_cancel_until is stored in the property timezone and evaluated by a worker running in UTC, so every daylight-saving boundary is an hour in which the system charges people it should not and refunds people it should. #### Start here **Why this design** Hotel booking is the reservation problem with a twist: the unit of sale is a room-night but the unit of purchase is a stay, so every booking is a range that must be sold entirely or not at all. Interviewers use it to probe concurrency control (how you stop two guests getting the last room on Saturday), read-heavy search over a write-contended table, and whether you recognise that some "double selling" is deliberate. Booking.com, Expedia and every hotel chain's own engine are built around this exact table. **What you will learn** - Why a stay is exploded into one inventory row per night, so the database can lock Saturday - and why an overlap query on (check_in, check_out) is where double-booking lives. - Why the booking write is one transaction across every night in the range: all the nights or none. - Why selling about 103% of the hotel is a priced decision against a 4-8% no-show rate, drawn as a component, and what walking a guest costs. - Why the biggest writer to inventory is not your guests: 347 channel-manager writes/s against 2.3 bookings/s, a 150:1 ratio. - Why search is 14,500 row reads per row written, and why the inventory table is a rolling 170 GB that never grows. - Why the cancellation deadline is stored on the reservation, not computed from today's policy. **How to read this board** - Press play once: the walkthrough follows one three-night stay from search to a cancellation. - SHOP: search over a range of nights, not a row. - DATA, then BOOK: per-night inventory and the all-or-nothing write. - THE OTHER WRITER: the channel managers that push more writes than your own customers. - CANCEL: the window is the product. - DATA MODEL: the range exploded into rows so it can be locked. - Then pages 1 to 6 in order: requirements, capacity, storage, availability, how it works, deployment. #### 1 · Requirements The unit of sale is a room-night. The unit of purchase is a stay. Almost everything difficult here comes from that mismatch. **Ask these before designing** - Are we the hotel or the agency? If we own the inventory we are the source of truth. If we are an OTA, we hold a cached copy of somebody else’s truth and every guarantee weakens accordingly. This board assumes we own it and also sell through agencies, which is the hardest of the three. - Is overbooking allowed, and who owns the number? It is a revenue-management decision with a cost line for walking guests. If the answer is "no", occupancy drops by 4-8% and somebody will ask why. - Do we hold inventory during checkout? For a 2-minute booking flow, a hold is often not worth the complexity; for a flow with an approval step, it is essential. - Whose timezone is the cancellation deadline in? The property’s, always - but the worker that enforces it runs in UTC, and that gap is a real bug twice a year. - Rate plans: how many, and do they restrict? Minimum stay, closed-to-arrival and non-refundable are not pricing, they are availability, and they belong in the search path. - What is the booking horizon? 500 days sets the size of the inventory table exactly. **Functional** - Search availability for a date range, a party size and a set of filters. - Book a stay: all nights or none. - Apply rate-plan restrictions (min-stay, closed-to-arrival) at search time, not at booking time. - Modify or cancel, applying the property’s cancellation policy and charging the penalty. - Accept inventory and rate updates from channel managers, and push our availability back to them. - Support a deliberate overbooking margin per property, per season. **Non-functional** - Search p95 under 400 ms across 50 properties x 20 room types x 3 nights - 3,000 inventory rows per query, which is why search reads a precomputed model and never the transactional table. - Booking is strongly consistent. All nights commit together or the booking fails. - Search availability 99.99%, degrading to a stale read model rather than an error. - Booking availability 99.95%, failing closed. - Channel updates applied within 60 s at p99. Beyond that, the agency is selling rooms against a view we know is wrong. **Explicitly out of scope** Dynamic pricing and revenue management models, loyalty programmes, housekeeping and room assignment, property onboarding, payments compliance. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps; a schema of 5 tables and 34 columns. ### Seat sales - fifty thousand people, one hundred seats - URL: https://tierslate.com/templates/ticketmaster-seats - Kind: system architecture board; level: deep - Access: paid, 800 AI credits - the question is public, the worked answer is not Why fifty thousand buyers for one hundred seats is a contention problem that sharding cannot solve. What is hard about it, left in on purpose: The waiting room does not remove the herd, it moves it: fifty thousand people now contend on one ordered structure per event instead of one seat key, and that structure is a single Redis shard because a position in a queue is meaningless unless one thing assigns it. If that shard is slow, nobody gets in and the sale looks like an outage; if it is lost, positions are lost and re-queueing an angry crowd is worse than the original problem. The hold TTL is the second unresolved choice: ten minutes is long enough to type a card number and long enough for a bot to squat the entire front block behind a single admitted session, and there is nothing on this board that can tell those two apart at hold time. The seat map is served from a cache that is seconds stale by construction, so the most common experience of a fast sale is clicking a seat that was already gone when the pixel was drawn. #### Start here **Why this design** Seat sales are the standard interview question about contention, as distinct from load. Fifty thousand people want the same hundred seats in the same second, and the trap is to answer with sharding and autoscaling when every one of them is fighting over one row on one shard. Interviewers use it to see whether you can tell a hot key from a high-traffic system, and real ticketing platforms have made headlines by failing at exactly this on an on-sale morning. **What you will learn** - Why the waiting room is most of the system: it turns 150,000 req/s into 300 admitted sessions/s and makes the herd a scheduling problem. - Why optimistic locking loses when conflicts are the normal case (500 claims on one seat, 499 retries), and why SET seat NX EX 600 with no retry wins. - Why UNIQUE (tickets.seat_id) is the only guarantee, and every layer above it is a performance optimisation. - How a hold TTL expires, gives the seat back mid-sale, and pushes it to everyone still watching the map. - Why a 300:1 burst at a scheduled second is a provisioning problem and not an autoscaling one, and why the contended state is only 25 MB. - Why an event is pinned to one region, and why deploys are frozen around the on-sale calendar. **How to read this board** - Diagram, read as a funnel: THE HERD (fans and bots at 10:00:00) and THE DOOR (waiting room, queue and admission gate) across the top, then THE HOLD (the atomic claim on one seat). Press play for the eight-step walkthrough. - Then the bottom row: CHECKOUT (the hold is a deadline) on the left, and WHEN THE CLOCK RUNS OUT (expiry and release) on the right. - Page 1 · Requirements - the product questions, and the one hard requirement: a seat is never issued twice. - Pages 2 and 3 · Capacity and Storage - the on-sale numbers (500,000 fans, 420 claims/s) and the 25 MB that is actually contended. - Page 4 · Availability - measured per on-sale, not per month, and what degrades rather than dies. - Page 5 · How it works - the sale told end to end - then Page 6 · Deployment plan, where the event calendar is the release process. - The DATA MODEL region between the diagram and these pages: five tables, and the constraint tickets.seat_id UNIQUE. - Finish on the deliberate weakness: the queue does not remove the herd, it moves it onto one ordered structure per event, and a ten-minute hold cannot tell a slow buyer from a squatting bot. That is the conversation to have. #### 1 · Requirements This is not a high-traffic system. It is a low-traffic system with a sixty-second window in which it is the busiest thing on the internet, and every requirement below is about that window. **Ask these before designing** - Is fairness a product requirement or a legal one? "First come first served" and "random draw among everybody present at 10:00:00" are different systems. A draw removes the thundering herd entirely and most promoters refuse it. - How long is the hold? Ten minutes is a card form on a phone on a train. It is also long enough for one admitted session to squat an entire front block. - Can a seat be resold after a failed checkout? If yes, the seat map must be push-updated, which is a second fan-out system. - Best-available or pick-your-seat? Best-available lets the server choose and collapses contention onto the server’s ordering. Pick-your-seat is what customers want and is strictly harder. - What is the bot policy? Rate limits, device attestation and a purchase cap are all product decisions with legal consequences, not middleware. - Does the queue survive a refresh? If a token is tied to a session cookie, a tab crash loses a place in line, which is the single most common support complaint. **Functional** - Show a seat map for an event, with prices and availability. - Admit users into the purchase flow at a controlled rate, with a visible, honest position. - Hold a chosen seat exclusively for a fixed window, then release it automatically. - Issue a ticket that is permanently bound to exactly one seat. - Enforce a per-account purchase cap for an event. **Non-functional** - A seat may never be issued twice. This is the only hard requirement. Everything else is negotiable under load. - Admission decisions in under 100 ms, because the waiting-room page polls and a slow queue reads as a broken sale. - Hold claim p99 under 50 ms, and it must FAIL fast: "taken" in 50 ms is a good outcome. - 99.95% availability during an on-sale minute, measured per on-sale rather than monthly - a 30-second outage at 10:00:00 is a total failure that a monthly average hides completely. - Peak-to-average on the hold path is about 300:1. Provisioned ahead of the on-sale from the event calendar; never autoscaled, because autoscaling reacts in minutes and this event lasts sixty seconds. **Explicitly out of scope** Payment processing internals, delivery and barcode rotation, secondary market resale, venue seat-map authoring, dynamic pricing. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps; a schema of 5 tables and 30 columns. ### Coupon codes - redeemable once, by anyone, at the same moment - URL: https://tierslate.com/templates/coupon-codes - Kind: system architecture board; level: starter - Access: paid, 200 AI credits - the question is public, the worked answer is not Why a single-use code is a race between every checkout holding it, and how a conditional write settles it. What is hard about it, left in on purpose: The conditional write settles the race by serialising it: every checkout using a public 10,000-use code updates the SAME row, and at about 3 ms of row lock per transaction that row tops out near 330 redemptions a second while 450 a second are queueing for it in the first minute of a launch - so the correct design is also the slow one exactly when it matters. The fifteen minute hold makes it worse in a different way: abandoned carts keep their reservations, so a code can read "fully redeemed" while 2,000 of its uses are holds that will be released, and shoppers are told it is gone and then watch it come back. The sweeper that releases holds must check the payment first, or a capture that succeeded while its commit write failed is released and resold. And "once per customer" is a unique index on customer_id, which a guest checkout with a fresh email address walks straight past. #### Start here **Why this design** Coupon codes are the friendliest version of a hard problem: a limited resource claimed concurrently by strangers. The same shape appears in ticket inventory, limited-edition drops and free trial slots. Interviewers like it because the bug in the obvious answer - read the count, check it, write count plus one - is easy to explain and easy to miss, and because the right answer (a conditional write) immediately raises the next two questions: what happens to the count while payment is pending, and what happens when forty thousand people want the same row at once. **What you will learn** - Why "is this code valid" is a cacheable read and "is there one left" is a serialised write, and why they are separate boxes. - The conditional UPDATE that settles a redemption race in one statement, and why an application-level lock is worse. - Reserve, commit, release: holding a redemption across a payment, and the three ways that count drifts. - Why one public code is one hot row, and two ways out - striped counters and per-customer unique codes. - Code generation: alphabets without look-alike characters, and why codes must not be guessable from each other. **How to read this board** - Press Play and follow the walkthrough from the SHOPPER along the top. - READ SIDE hangs down from the coupon service; THE RACE is on the right. - PAYMENT sits under the race, because a redemption only becomes real after capture. - ISSUANCE at the bottom left, with the cloud every code eventually leaks to. - 1 · Requirements to 6 · Deployment in the written band, then the DATA MODEL. - Then argue with the tension: the correct write is a hot row, and the fair hold is a count that lies. #### 1 · Requirements **Ask these before drawing anything** - Is the code shared (SAVE20, used by anyone up to a cap) or unique (one code per recipient, used once)? These are different systems. A shared code is one hot counter. Unique codes are millions of rows with no contention at all. - Is the limit global, per customer, or both? "Once per customer" needs a notion of customer that guest checkout does not have. - Does an unpaid checkout use up a redemption? If yes, abandoned carts burn the campaign. If no, two shoppers can both be shown the discount and only one can pay with it. - Can codes stack? Stacking turns validation into an ordering problem (percent off before or after fixed off) and should be answered "no" unless somebody insists. - Is going over the cap a disaster or an accounting note? Ten extra uses of a 20 percent code is a rounding error. Ten extra uses of a free-iPhone code is a press story. **Functional** - Apply a code to a cart: return the discount or a specific reason (expired, minimum spend not met, already used, none left). - Reserve one redemption when the shopper starts payment; commit it when payment is captured; release it if payment fails or the hold expires. - Enforce a global cap and an optional per-customer cap, exactly - never over. - Marketing can create a campaign with a shared code, or bulk-generate up to 10 million unique codes. - Disable a leaked code immediately without a deploy. **Non-functional** - Apply (validation only) p99 under 30 ms; it runs on every cart change. - Reserve p99 under 100 ms in normal traffic. Under a hot-code launch, bounded queueing rather than timeouts. - Zero over-redemption. This is the one property that is not negotiable. - Holds released within one minute of expiry. - Unique codes not enumerable: knowing one code tells you nothing about another. **Out of scope** - Pricing and tax. The coupon service returns a discount; the order service computes the total. - Gift cards and store credit. Those are money, with a ledger; coupons are a promise. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps; a schema of 4 tables and 33 columns. ### Rental search - availability is a calendar, not a column - URL: https://tierslate.com/templates/rental-search - Kind: system architecture board; level: core - Access: paid, 400 AI credits - the question is public, the worked answer is not How a stay search filters by place, dates and price at once, and why the availability calendar is the bottleneck. What is hard about it, left in on purpose: The availability bitmap in the index is about 90 seconds behind our own calendar, which is fine - booking re-checks every night under a lock. The problem is the calendar itself is hours behind reality for any listing that is also on another site: iCal imports are polled every three hours, so a night sold on the other platform at 10:00 is still AVAILABLE in our calendar at 12:59, the booking re-check passes, and the double booking is invisible to us until the host cancels on the guest. Nothing here can close that window without the other site pushing to us, and it does not. The bitmap also answers only "free on these nights" - minimum-stay and check-in-day rules are evaluated after the AND, on the candidates that survived it, so a listing with a 7-night minimum passes a 3-night mask and is dropped late, and a search near a holiday can lose a third of its first page to that second pass and come back short. #### Start here **Why this design** A rental marketplace is a search engine whose most important filter changes every time somebody books anything. Place is a geo index; guests and amenities are columns; but availability is a property of individual nights on a calendar, and price is the sum of those nights. Interviewers use it to see whether you can keep a per-night calendar inside a search index cheaply - and whether you notice who else writes to that calendar. **What you will learn** - Why "available 12-15 March" is three bits, not a column, and why the whole calendar fits in 46 bytes. - Why the filters run in order place, dates, price, rank, each on fewer candidates than the last. - Why a price filter needs nightly prices in the index, because the total is different for every range. - Why search may be 90 seconds stale while booking must re-check every night under a lock. - Why the hardest writer to the calendar is another website, polled every three hours. - Why minimum-stay rules are a second pass after the bitmap, and what that costs a search page. **How to read this board** - Press play once: the walkthrough follows one search to one booking, then one calendar edit back into the index. - SEARCH: the pipeline along the top. - THE CALENDAR: the source of truth and its three writers. - DATA MODEL: one row per listing per night, and the imported calendars. - Then pages 1 to 6 in order: requirements, capacity, storage, availability, how it works, deployment. #### 1 · Requirements A guest describes a stay; the system must find every listing that can host exactly that stay, at a price they will pay, fast enough that dragging the map feels live. **Ask these before designing** - Is the result exact or best-effort? Search showing a listing that turns out to be booked is recoverable; booking a night twice is not. This board makes search approximate and booking exact. - Is price filtered on the nightly rate or the total? Guests think in totals with fees. Totals depend on the range, so the index needs per-night prices. - Are listings on other sites too? Most professional hosts are. Then our calendar imports theirs, and the import interval is an availability bug of that many hours. - Which stay rules exist? Minimum nights, maximum nights, allowed check-in days, preparation days between stays. Each is an availability rule disguised as a setting. - How far ahead can you book? 365 days sets the bitmap at 365 bits. **Functional** - Search by map bounds or place name, check-in and check-out, guest count, price range and amenities. - Results ranked, with the total price for the stay shown on every card. - Hosts edit availability and nightly price per night, and set stay rules. - Hosts connect external calendars (iCal) that block nights here. - Book a stay: every night must be free at the moment of booking. **Non-functional** - Search p95 under 300 ms, including map drags, at 5,800 searches/s peak. - Index freshness p99 under 2 minutes after a calendar change we receive. - No double booking from our own sales - enforced by the database, not by search. - Search availability 99.95%; it may serve a slightly stale index rather than fail. - Booking availability 99.95%, failing closed. **Out of scope** Payments and payouts, reviews, messaging, pricing suggestions for hosts, fraud and trust, photos and their CDN. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 7 steps; a schema of 5 tables and 38 columns. ### Job board - search, apply, and the recruiter's inbox - URL: https://tierslate.com/templates/job-board - Kind: system architecture board; level: core - Access: paid, 400 AI credits - the question is public, the worked answer is not How listings are indexed for search and applications are routed, and why expired jobs must disappear fast. What is hard about it, left in on purpose: The board makes a CLOSED job disappear within minutes, but it can only close a job somebody tells it about. A feed job that vanishes from its ATS is noticed at the next poll, up to four hours later, and a directly posted job that was filled last week but never closed stays live until valid_through - thirty days by default - collecting applications from people who will never hear back. Nothing here detects that ghost job, even though the signal is on the board: an inbox nobody has opened for fourteen days. The inbox has its own unanswered question: a remote role takes 2,000 applications in 48 hours, a recruiter reads about the first hundred, and the default sort is newest first - so whether a candidate is seen is decided by the order the board renders a list, not by anything about the candidate. And a webhook to an ATS that answers 200 and then drops the application marks it DELIVERED into a void. #### Start here **Why this design** A job board is a full-text and geo search over a catalogue that turns over completely every month, plus a delivery system that carries applications to wherever the recruiter really works. Interviewers use it to see how you index fast-changing documents, how you make deletion reliable rather than eventual, and whether you notice that most listings are imported from somebody else's system on a timer. **What you will learn** - Why removing a closed job from search is the product's freshness promise, not housekeeping. - Why expiry has two mechanisms: an outbox delete and a valid_through filter on every query. - Why 80% of jobs arrive by feed, re-fetched every four hours, and why a content hash makes that cheap. - Why an application is routed to the employer's ATS or an inbox, and is one row per candidate per job. - Why a hot remote job takes 2,000 applications in 48 hours, and what the inbox's sort order decides. - Why a job that is filled but never closed is the failure this board cannot see. **How to read this board** - Press play once: the walkthrough follows a search, an application, and then a job closing. - FIND & APPLY: query, freshness filter, index, apply. - THE RECRUITER'S INBOX: routing to an ATS or the board's own inbox. - POSTINGS & EXPIRY: direct posts, feed imports, the outbox, the sweeper and the indexer. - DATA MODEL: jobs with their own expiry date, and applications unique per candidate. - Then pages 1 to 6 in order: requirements, capacity, storage, availability, how it works, deployment. #### 1 · Requirements Two customers with opposite needs: seekers want every live job and no dead ones; recruiters want the right hundred applications, not two thousand. **Ask these before designing** - Where do jobs come from? Direct posts by recruiters, or feeds from applicant tracking systems? Usually both, with feeds the large majority. - Where do applications go? Our own inbox, the employer's ATS by webhook, or a redirect to the employer's site? Each has a different delivery guarantee. - How fast must a closed job disappear? Minutes is achievable for jobs we are told about. Jobs we are not told about are the real problem. - Is a listing paid? A paid listing has a hard end date, which is the one expiry you can always trust. - Can a candidate apply twice to one job? No - and a double tap on Apply must not send two. **Functional** - Search by keywords, location radius, remote, salary range, seniority, posted date. - Apply with a stored résumé and screening answers in one step. - Route each application to the employer's ATS or to the recruiter inbox. - Recruiter inbox: list, sort, filter and change stage on applications. - Recruiters post, edit and close jobs; employers' ATS feeds are imported on a schedule. - Jobs expire automatically at valid_through. **Non-functional** - Search p95 under 250 ms at 8,300 queries/s peak. - Closed or expired jobs out of search within 5 minutes of the board knowing; and never shown past valid_through. - Application accepted exactly once per candidate per job, durably, before the seeker sees "sent". - ATS delivery p99 under 2 minutes, retried for 72 hours. - Search availability 99.95%; apply 99.9%. **Out of scope** Recommendation and matching models, sponsored-listing auctions, messaging, interview scheduling, salary estimation, employer reviews. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps; a schema of 5 tables and 36 columns. ## Infrastructure primitives ### URL shortener - one write, a million reads - URL: https://tierslate.com/templates/url-shortener - Kind: system architecture board; level: starter - Access: paid, 200 AI credits - the question is public, the worked answer is not Why one tiny write and a hundred cacheable reads make two systems sharing a single table. What is hard about it, left in on purpose: Uniqueness is bought with a single global counter: every write in the world waits on one INCR, that counter is the only node here with no replica, and base62 of a counter makes every link on the service enumerable by walking the range. Read scale is bought with the opposite mistake - the CDN caches the 301, so after the first visitor a deleted or re-pointed link keeps redirecting until the TTL expires, and the clicks that never reach the service are never counted. #### Start here **Why this design** The URL shortener is the most common opening question in a system design interview because the product fits in one sentence and the hard parts do not. It probes whether you start from requirements and numbers, whether you see that a 100:1 read-to-write ratio means two different systems sharing one table, and whether you can defend a key-generation scheme and an HTTP status code under questioning. In real life the same shape - tiny write, enormous cacheable read - is every link tracker, QR code service, feature-flag lookup and DNS-like resolver. **What you will learn** - How to turn 100M new links a month into ~40 writes/s, ~3,900 redirects/s on average and ~19,000/s at peak - and why bandwidth is not the constraint here. - Why a 7 character base62 key (62^7 ≈ 3.5 trillion) outlasts ten years of 1.2 billion links a year with 99.7 percent of the space unused. - Counter-based keys against random keys: collision-free and short versus no single point of failure and not enumerable - and the range-leasing middle ground. - Why the read path sits behind two caches, and what a cached redirect costs: stale deletes and clicks that are never counted. - 301 against 302: the status code decides your server load, your analytics accuracy and whether you can ever take a link down. **How to read this board** - Diagram first. WRITE PATH · CREATE A LINK across the top: author, client, the Shortener API hex, the key encoder and the red counter. - Then DATA on the right: the link store, written once by the top half, and the hot key cache in front of it for the bottom half. - Then READ PATH · FOLLOW A LINK across the bottom: visitor, CDN edge, the redirect service hex, and the 301 looping back. Then ANALYTICS · OFF THE PATH: click events, click store, link analytics. Press play for the eight step walkthrough. - Then the pages in order: 1 · Requirements, 2 · Capacity estimation, 3 · Storage estimation, 4 · Availability, 5 · How it works, 6 · Deployment plan. - Last, DATA MODEL under the pages: the links row and the click_daily rollup, as real tables that Generate SQL turns into DDL. - The conversation to have is the red box and the CDN. Uniqueness is bought with one counter that has no replica and makes every link enumerable; read scale is bought with an edge-cached 301 that keeps deleted links alive and never counts the clicks it serves. Page 5 argues both trades; decide which you would change first. #### 1 · Requirements Write the requirements before drawing anything: the two non-functional lines about read latency and key uniqueness decide nearly every box on the diagram. **Functional** - Create: given a long URL, return a short link https://sho.rt/{key}. The same long URL submitted twice may return two different keys unless the caller asks for dedup. - Redirect: GET /{key} answers with an HTTP redirect to the long URL, or 404 if the key is unknown, deleted or expired. - Optional custom alias (sho.rt/launch) and optional expiry date, both chosen at creation. - The owner can delete or disable a link. Abuse reports can disable one centrally (malware, phishing). - Per-link analytics: clicks by day, referrer and country - approximate is acceptable. - Authenticated API with per-key rate limits for creation, since a shortener is a favourite tool for spam. **Non-functional** - Redirect latency: p99 under 50 ms at the origin, under 20 ms when served from the edge. A redirect is an extra hop before the page the visitor actually wanted. - Redirect availability 99.99 percent (about 4 minutes a month). A dead redirect breaks every link ever printed on a poster or in an email. - Create availability 99.9 percent. A failed create is retried by a person; a failed redirect is not. - Uniqueness is absolute: two links may never share a key, and a key is never reused after deletion. - Durability: a link that has been returned to its author must survive any single machine, zone or region loss. - Reads may be eventually consistent by seconds; a newly created link must work on its first click (read-your-write for the author). - Analytics are best-effort and eventually consistent; they must never slow a redirect. **Out of scope** - Link previews, QR rendering, branded domains per customer and A/B routing - each is a feature on top of the same lookup. - Exact, billable click counts. If clicks are money (ad networks), the design changes: no edge caching and a durable click log. - Content scanning of the destination page beyond a blocklist check at create time. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps; a schema of 2 tables and 10 columns. ### Distributed cache - the ring, and what a node leaving costs - URL: https://tierslate.com/templates/distributed-cache - Kind: system architecture board; level: core - Access: paid, 400 AI credits - the question is public, the worked answer is not Why a hash ring moves a sliver of keys when a node leaves, and why the hot key and the miss storm remain. What is hard about it, left in on purpose: Consistent hashing balances how many KEYS each node holds. It has nothing to say about how many REQUESTS each key gets, so one celebrity key is one slot on one node and no rebalancing can split it - the only fixes are to replicate that key to every node or to cache it in the application, and the near-cache drawn here has no invalidation at all, so it trades a hot spot for stale reads nobody can clear. The second cost is worse because it looks like success: removing one node of sixteen moves only 6.25 percent of the keyspace, and every one of those keys misses 100 percent of the time until it is refilled, so a routine rolling restart doubles the load arriving at the origin database while the dashboard reports a 94 percent hit rate and looks fine. #### Start here **Why this design** A distributed cache is the most common component put in front of a database and the one most often misunderstood, because the hard part is not storing bytes in RAM - it is deciding which machine owns a key with no coordinator, and surviving the moment that answer changes. Interviewers use it to see whether you know why consistent hashing beats modulo, whether you notice that it balances keys rather than traffic, and whether you treat the hit rate as a capacity multiplier on the origin rather than as a performance nicety. Teams running one in production meet the same three failures this board draws: a celebrity key, a miss storm when a node leaves, and a cache that has quietly become load bearing. **What you will learn** - Why hash(key) mod N moves 94 percent of keys when N goes from 16 to 17, and why a ring with 160 virtual nodes per member moves only 1/N and spreads it evenly. - How the client library computes placement locally in microseconds, and why the membership view - not any server - is the only shared state that matters. - Why one point of hit rate is about 20 percent more origin load, and why the real availability requirement is the origin surviving a 0 percent hit rate. - Why a hot key cannot be fixed by rebalancing, and what the two real fixes - replicate-everywhere and an in-process near cache - each cost. - How single-flight loading and warming a joining node turn a miss cliff into a curve during routine rolling restarts. - Why the only durable schema a cache has is its control plane: topology versions, members, namespace policy and hot-key samples. **How to read this board** - Diagram, left to right along the read path: CALLER · AND ITS OWN CACHE, then PLACEMENT (the client library and the ring hex), then THE RING · FOUR OF SIXTEEN (the members, node D about to leave). - Then the right column: WHAT THE RING DOES NOT SOLVE (hot key, eviction, single flight) and ORIGIN · SIZED FOR 5% OF READS. Then MEMBERSHIP along the bottom, off the request path. Press play for the eight-step walkthrough. - Page 1 · Requirements - the questions that decide whether this is a cache at all, and the hit-rate and latency targets. - Pages 2 and 3 · Capacity and Storage - traffic, the ring size (sized by RAM, not throughput), bandwidth, the hot key, and why durable storage is zero. - Page 4 · Availability - the two targets, and what one, four or all sixteen nodes going away does to the origin. - Page 5 · How it works - one get told end to end, then Page 6 · Deployment plan - regions, warming, and why a hash change is not rollable. - Finally the DATA MODEL below the band: the four control-plane tables, because a cache has no schema of its own. #### 1 · Requirements **Ask these before drawing anything** - What is being cached, and what does a stale answer cost? A stale product description is nothing; a stale permission check is a security incident. This answer decides whether TTL alone is acceptable or whether explicit invalidation is mandatory. - Is this a cache or a store? A cache may lose everything and the system still works, slowly. If losing it loses data, it is not a cache and consistent hashing is the wrong tool - use the key-value store board instead. - What is the working set, and what is the total key space? Caching is only economic when a small fraction of keys serves a large fraction of reads. If access is uniform, a cache is just expensive RAM. - How big is the largest value, and how hot is the hottest key? Both have answers that break the design, and both are usually unknown until somebody measures. - Who invalidates - the writer, a TTL, or nobody? Nobody is a valid answer and should be said out loud rather than discovered. - Can the origin survive a total cache loss? If not, the cache is load bearing, and the availability requirement is really the origin capacity requirement. **Functional** - get, set and delete on an opaque byte value, keyed by an opaque string. - A per-key TTL, set by the caller, defaulting per namespace. - Read-through on a miss, with exactly one fill per key regardless of how many callers missed at once. - Add and remove nodes without restarting clients and without moving more than one node share of the keyspace. - Report per-key request counts well enough to identify a hot key while it is hot, not the next morning. **Non-functional** - p99 of 1 ms for a hit, measured at the client including the network. If a cache is not a great deal faster than the origin it is a liability rather than an optimisation. - Hit rate at or above 95 percent in steady state. Everything below that arrives at a database sized for 5 percent. - 99.9 percent availability for the cache, and a system that stays UP at 0 percent cache availability. Those two numbers are different requirements and the second is the important one. - Adding or removing a node moves at most 1/N of keys and needs no client restart. - The cache never becomes the source of truth. Any value in it can be recomputed from the origin. **Out of scope** - Durability. Nothing here is written to disk, and that is a feature - it is why a node restart is quick and why the capacity maths is only about RAM. - Cross-region replication of cache contents. Each region caches for itself; see the deployment page. - Transactions across keys. A cache with transactions is a database wearing a disguise. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps; a schema of 4 tables and 24 columns. ### Key-value store - quorums, and where the consistency knob sits - URL: https://tierslate.com/templates/key-value-store - Kind: system architecture board; level: deep - Access: paid, 800 AI credits - the question is public, the worked answer is not How a key finds its replicas, and why choosing how many to wait for buys overlap rather than order. What is hard about it, left in on purpose: R + W > N guarantees that a read touches at least one replica that saw the write. It does NOT order two concurrent writes, so the coordinator regularly gets two versions back and something has to choose - last-write-wins discards one of them silently and stakes correctness on clocks that are not synchronised, while vector clocks hand the choice to an application that usually has no idea what to do with two shopping carts. Worse, the knob is per REQUEST and not per cluster: one ONE-level read in a hot code path makes the whole store behave as eventually consistent regardless of what operations configured, and nothing in the system reports that it happened. The deletes are the third trap - a tombstone that is garbage collected before a slow replica hears about it lets the deleted row come back. #### Start here **Why this design** A distributed key-value store is the question interviewers use to find out whether you understand replication or can only name it. Dynamo, Cassandra and Riak all answer the same three questions - where does a key live, how many copies do you wait for, and who decides when two copies disagree - and the interview probes whether you know that R + W > N buys overlap rather than ordering, that the consistency level is chosen per request rather than per cluster, and that a delete in a leaderless store is a write. Teams running these stores in production get burned by exactly those three points. **What you will learn** - How a partition key becomes a token and a preference list of 3 DISTINCT PHYSICAL nodes, with 256 vnodes per machine and rack-aware placement so N = 3 really means three. - Why W = 2 of N = 3 still sends three writes, and why waiting for the second-fastest replica makes QUORUM both safer and faster than ALL. - What R + W > N actually guarantees - overlap, not order - and what last-write-wins and vector clocks each cost when two versions come back. - Three repair mechanisms at three timescales: hinted handoff for 3 hours, read repair on every read, Merkle-tree anti-entropy as the backstop. - Why a tombstone needs a 10-day gc_grace, and why a node down longer than that is rebuilt and never rejoined. - The sizing: 104,000 ops/s at peak and 16 nodes by throughput today, storage-bound at 48 nodes by year five - and the TTL that is worth 34 of them. **How to read this board** - Press play once: eight steps follow one put and one read, and the first step is the consistency level, not the coordinator. - CALLER · THE KNOB IS HERE, NOT IN THE CLUSTER: the consistency level is a parallelogram because it travels inside the request. - COORDINATE · ANY NODE, NO LEADER: the coordinator, the partitioner, and the two diamonds where W and R are counted. - THE REPLICA SET · N = 3, NONE OF THEM PRIMARY: three equal replicas, the slow third one in red, and the commit log to SSTable write path. - WHEN A REPLICA IS DOWN, then WHEN REPLICAS DISAGREE: two different problems with different machinery - hints and Merkle trees for the first, version reconciliation, read repair and tombstones for the second. - Pages 1 to 6 in order: requirements, capacity, storage, availability, how it works, deployment. - DATA MODEL: the cluster metadata - keyspaces, cluster_nodes, token_ranges, hinted_writes - because the values themselves are opaque bytes. #### 1 · Requirements **Ask these before drawing anything** - What is the access pattern - get and put by a single key, or are there range scans and secondary lookups? A store that must answer "all orders for this customer, newest first" is a different design, and bolting a secondary index onto this one is the most common way to ruin it. - What does a stale read cost? This is the question the whole board turns on. If a stale read is merely annoying, the default can be ONE and the system is fast and cheap. If it is money or a permission, every read pays a quorum. - Can the application resolve conflicts? Vector clocks are only useful if somebody downstream knows how to merge two versions. If the answer is no, last-write-wins is the honest choice and its cost must be written down rather than avoided. - How large is a value, and is it ever appended to? Large values and growing values are different failure modes: one blows the row size, the other makes compaction never finish. - What is the delete volume? Deletes are writes here, and a high-delete workload is the one that turns compaction into the bottleneck. - Single region or multi? Cross-region quorums cost 80 ms per operation, which usually means the answer is local quorums plus asynchronous replication, which is a weaker guarantee and must be said out loud. **Functional** - get(key) and put(key, value) on an opaque value, plus delete. - Tunable consistency per REQUEST, not per cluster: ONE, QUORUM, or ALL. - Replication factor of 3, configurable per keyspace. - A node may be added or removed without downtime and without moving more than its share of the token space. - Survive the loss of any single node with no loss of availability for reads or writes at QUORUM. - Repair divergence automatically - on read, in the background, and after an outage. **Non-functional** - p99 of 10 ms for a QUORUM read, 15 ms for a QUORUM write, measured at the coordinator. - 99.99 percent availability for QUORUM operations, which given N=3 means surviving one replica down per partition and no more. - Durability: a write acknowledged at QUORUM must survive the immediate loss of any one node. That is a statement about fsync of the commit log, not about replication, and the two are frequently confused. - Linear scale-out: doubling the nodes doubles the throughput and halves the per-node data, with no rewrite of existing data beyond the moved token ranges. - No single point of failure and no elected leader on the request path. **Out of scope, and each of these is a whole other system** - Multi-key transactions. Not supported, not partially supported, and not emulated in the client. - Secondary indexes. A global secondary index over a partitioned store is a distributed transaction wearing a friendly name. - Range scans across partitions. The partitioner destroys ordering by design. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps; a schema of 4 tables and 30 columns. ### Rate limiter - token bucket, and where the counter lives - URL: https://tierslate.com/templates/rate-limiter - Kind: system architecture board; level: core - Access: free - everything below is the whole board Why the hard part of limiting requests is where the shared counter lives, not the algorithm. What is hard about it, left in on purpose: Every request, including the 98 percent that are allowed, pays a network round trip to a counter it must WRITE, so the limiter adds latency to exactly the traffic it exists to protect. That counter shards by key, which means a single popular tenant is a single hot slot no cluster can split. The per-instance token cache on the board removes the round trip and breaks the limit instead: nine gateway instances each enforcing 100 per second is a limit of 900. And nothing here decides what happens when the counter store is unreachable - fail open and the limit silently does not exist, fail closed and a Redis blip becomes a full outage of a service that was perfectly healthy. #### Start here **Why this design** A rate limiter is the most common "small" system-design question, and it is small only until you ask where the counter lives. Interviewers use it to test three things at once: whether you can pick a key (who is being limited), whether you can pick an algorithm and say what it allows at the boundary, and whether you notice that a shared counter turns every request - including the 98% that are allowed - into a network write. Every public API, login form and internal service mesh has one, and most production incidents involving them are about the counter, not the maths. **What you will learn** - Why the limit key (API key + route class + tier) is the decision everything else follows from - the shard, the hot spot and who gets punished behind a shared IP. - Token bucket versus sliding window in two numbers each, and the fixed-window boundary burst that lets a caller spend twice its limit inside one second. - Why a limiter has no reads: every decision is an atomic read-modify-write on shared state, and what that costs inside a 2 ms p99 budget at 17,000 requests a second. - Why sharding cannot fix a hot key, and what splitting one tenant into 16 sub-buckets costs in accuracy. - Fail open or fail closed when the counter store is down, regional rather than global limits, and the 429 with Retry-After that stops a retry storm. **How to read this board** - Press Play and follow the walkthrough through the diagram: ARRIVES (who is this) along the top left, DECIDE (the algorithm), then THE TWO ANSWERS and COUNTER STATE on the right. - Then OFF THE PATH along the bottom of the diagram - the queue and rollup that tell you whether the limits were right. - 1 · Requirements - the questions that decide the key and the algorithm. - 2 · Capacity estimation and 3 · Storage estimation - why one Redis shard carries the traffic and why the hot key still hurts. - 4 · Availability - what degrades rather than dies when the counter store is slow or gone. - 5 · How it works - the request path in prose - then 6 · Deployment plan. - Finally the DATA MODEL at the bottom, then argue with the weaknesses this board leaves in on purpose: the per-instance token cache that turns a limit of 100 into 900, and the counter store nobody decided how to lose. #### 1 · Requirements **Ask these before drawing anything** - What is being protected - a public API, an internal service, or a login form? A login limiter is an abuse control and is allowed to be strict and rude. An API limiter is a product feature that customers build against, so it has to be predictable and documented. - What is the limit ON: an API key, an end user, an IP, a tenant, or a route? This one answer decides the key, and the key decides the shard, the storage and the hot spot. Everything else on the board follows from it. - Is the limit a hard ceiling or a fairness mechanism? A ceiling may reject. Fairness should queue or shape, and rejecting is the wrong tool. - Must the limit be exact, or is ten percent over acceptable? Exactness is the single most expensive requirement in this design and it is almost never needed. - May the limiter fail open? If nobody has decided this in advance, it will be decided at three in the morning by whoever is on call. - One region or several? A globally exact limit costs a cross-region round trip on every request, which is roughly eighty milliseconds. - What is a denied caller told? Headers, body shape, and whether the denial is billable. **Functional** - Decide allow or deny for every request before it reaches the upstream service. - Support more than one algorithm at once: a token bucket for burst control and a sliding window counter for the hourly quota. A request must pass both. - Limits are defined per tier and per route class, with per-customer overrides, and are editable at runtime without a deploy. - A denial returns HTTP 429 carrying Retry-After and the remaining quota. - Every decision, allowed or denied, produces one event for later review. **Non-functional** - Availability 99.99 percent for the decision path. The limiter sits IN FRONT of the service, so its availability multiplies the availability of everything behind it - it has to be more available than the thing it protects. - Added latency budget: 2 ms at p99, counter round trip included. - Accuracy: never more than ten percent over the stated limit in steady state. Exactness is not a requirement, and buying it is the classic mistake here. - Scale to 35,000 decisions per second with no instance holding state that matters. - A limit change is in effect everywhere within 30 seconds. **Out of scope, stated so nobody assumes it** - Authentication. The key must already exist by the time the limiter runs. - Bot detection and WAF rules. Different signal, different system. - Billing for overage. The limiter emits events; pricing reads them. #### 2 · Capacity estimation **The traffic** DAU 10,000,000 MAU 25,000,000 (2.5 x DAU) API calls per DAU per day 50 ------------------------------------------------- requests/day = 10,000,000 x 50 = 500,000,000 average QPS = 500,000,000 / 86,400 = 5,787 peak QPS = 5,787 x 3 = 17,361 provision at 2 x peak = ~35,000 **The read to write ratio is the whole problem** a cache: reads 100 : writes 1 a news feed: reads 50 : writes 1 a rate limiter: reads 0 : writes 1 There is no such thing as a read here. Asking whether request 101 is over the limit means having counted the hundred before it, so every single decision is a read-modify-write against state that all instances share. That sentence is the reason this design is hard and the reason a local cache cannot fix it. **What that does to the counter store** atomic INCR calls/s at peak = 17,361 token bucket : 1 key touched per call sliding window : 2 keys touched per call, one script Redis, simple ops, single node ~ 100,000 /s => one shard carries peak at ~17% utilisation round trip, same AZ = 0.4 ms p50, 1.8 ms p99 latency budget = 2.0 ms p99 => it fits, with nothing at all to spare Three shards are provisioned anyway, not for throughput but so that losing one costs a third of the keyspace rather than all of it. **The hot key, which sharding does not solve** largest tenant = 8% of traffic = 1,389 QPS at peak all of it under ONE key -> ONE slot -> ONE shard a cluster splits KEYSPACES, never a single key. fix: shard the key itself - rl:{tenant}:{0..15}, each holding limit/16 the caller is hashed to one of the 16 at the edge cost: the limit is now 16 buckets, so a caller that lands unevenly is throttled below its real limit **Gateway fleet** 17,361 peak QPS / 2,000 QPS per instance = 9 instances run 18, split across two AZs, so losing an AZ loses headroom rather than the service #### 3 · Storage estimation **Counter state is bounded, and that is the point** active keys = 10,000,000 callers x 5 route classes = 50,000,000 keys per key: name 40 B + int64 8 B + store overhead 60 B = 108 B token bucket : 50,000,000 x 108 B = 5.4 GB sliding window : 100,000,000 x 108 B = 10.8 GB (current + previous window) TTL = 2 windows, so expired keys leave. This figure is a function of ACTIVE CALLERS, never of elapsed time. It does not grow with age. provision: 3 shards x 8 GB, 1 replica each = 6 nodes **Policy, and saying out loud that it is nothing** 4 tiers x 5 route classes x 200 B = 4 KB per-customer overrides 2,000 x 200 B = 400 KB => it fits in the memory of every gateway instance, which is why it is loaded rather than queried **Decision events, which is the unbounded one** events/day = 500,000,000 (one per request) per event = 120 B key id 16 + route 8 + ts 8 + verdict 1 + remaining 4 + region 2 + framing/compression pad raw/day = 500,000,000 x 120 B = 60 GB/day 1 year raw = 60 GB x 365 = 21.9 TB 5 years raw = 60 GB x 1,825 = 109.5 TB columnar 5:1 = 4.4 TB / 21.9 TB Keeping five years of every allowed request is not a design, it is an invoice. **So the retention policy is the storage design** raw, 7 days = 60 GB x 7 = 420 GB then roll up to hourly, and only for keys that were DENIED at least once (about 2% of keys): 1,000,000 keys x 24 h x 48 B = 1.15 GB/day 1 year = 420 GB 5 years = 2.1 TB steady state total = ~2.5 TB on object storage versus 109.5 TB if nothing is ever thrown away The rollup keeps only denied keys because the allowed ones answer no question anybody asks. Nobody has ever opened a dashboard to find out which customer was comfortably under its limit. #### 4 · Availability **The target, and why it is stricter than the service behind it** 99.99 percent, which is 52 minutes a year. That is higher than the 99.9 percent the upstream API promises, and the reason is arithmetic rather than ambition: the limiter is in series with everything behind it, so the availability the customer experiences is the product of the two. A limiter at 99.9 percent in front of an API at 99.9 percent gives the customer 99.8 percent, and the limiter has then spent half the error budget of a service it was supposed to protect. **Single points of failure, named** - The counter shard holding a given key. It is the only component that cannot be made read-only or cached away, because the decision is a write. - The policy store, but only at process start. Once loaded, limits live in memory. - The gateway fleet itself, which is the limiter - it is a library inside the gateway, not a service behind it, precisely so that it cannot fail separately. **Replication and failover** Three counter shards, one replica each, replication asynchronous. Failover promotes a replica in about ten seconds. Asynchronous replication means the promoted replica is a few hundred milliseconds behind, so a handful of callers get more requests through than they should during a failover. That is the correct trade and it should be said out loud: a limiter that is slightly wrong for 200 ms is fine, a limiter that is unavailable for 200 ms is not. **What degrades rather than dies** - Counter shard slow, over 5 ms: a circuit breaker opens and the decision falls back to the per-instance token bucket at limit divided by instance count. The limit becomes approximate. It stays enforced. - Counter shard down: the same, plus an alert. Worst case aggregate enforcement drifts to instance count times the limit, which for 18 instances is an eighteen-fold ceiling - bad, and still a ceiling. - Policy store down: keep the last loaded policy in memory indefinitely. Limits stop changing. They do not disappear. - Decision queue full: drop the events. Telemetry is the only thing on this board permitted to be lossy, and it is marked with a dashed wire for that reason. - Total loss: a documented kill switch that hard-allows, with the blast radius written down in advance so that nobody has to reason about it during the incident. **What must never degrade** The timeout. Five milliseconds, hard, no retry. A limiter that hangs waiting on a slow counter converts a storage problem into thread pool exhaustion in the gateway, and the gateway is carrying the allowed traffic too. More outages come from limiters waiting politely than from limiters being wrong. #### 5 · How it works A request arrives at the edge gateway, which terminates TLS and routes it. Nothing has been counted yet, and that ordering is deliberate: the limiter needs an identity, so authentication runs first. Floods of unauthenticated traffic are therefore not this system to solve - they belong to the WAF in front of it. The gateway then builds the limit key, and this is the design decision everything else falls out of. The key is the API key, the route class and the tier joined together. Limiting by IP instead would punish an entire office behind one NAT while doing nothing at all to an attacker holding a block of IPv6 addresses. With a key in hand the limiter loads the numbers for that tier and route from an in-memory copy of the policy store, refreshed in the background. Limits are rows rather than constants because the first thing anybody wants during an incident is to raise one customer limit without shipping a deploy. The token bucket runs first. A bucket holds a hundred tokens and refills at ten a second; the entire state is a token count and the time it was last refilled, so the refill is arithmetic performed on read rather than a timer ticking somewhere. This is the algorithm that permits bursts deliberately - a caller who has been quiet for a minute may spend a hundred requests at once, which is what a human clicking looks like. The sliding window counter runs second, against the hourly quota. A plain fixed window would let a caller spend the full quota at 11:59:59 and the full quota again at 12:00:00, which is twice the limit inside one second. The sliding window keeps the previous window too and weights it by how much of it is still in view, removing the boundary burst for the price of one more integer. Both algorithms need the same thing: a counter that every gateway instance shares. That counter is the expensive part of this system. The check and the increment must be a single atomic operation the store performs itself, because reading the value, deciding, and writing it back lets two concurrent requests both observe 99 and both be allowed. The cost is a network round trip on every request, including the 98 percent that will be allowed - a limiter taxes the traffic it is protecting. Allowed requests continue to the upstream API. Denied ones get a 429 carrying Retry-After and the remaining quota, because a denial that tells the client nothing is retried immediately, and the limiter then spends the rest of the incident counting retries of retries. Finally, every decision is dropped on a queue that nothing waits for. It drains into a usage history that is rolled up hourly for the keys that were actually denied. That history is the only mechanism by which anybody ever discovers the limits were set wrong, which makes the cheapest component on the board the one that decides whether the system is any good. #### 6 · Deployment plan **Regions** Three: us-east-1, eu-central-1, ap-southeast-1. The counter store is REGIONAL, deliberately not global. A globally exact limit would mean a cross-region round trip of roughly eighty milliseconds on every request, which is forty times the entire latency budget. So each region enforces its own share of the limit and a background job reconciles the global total every thirty seconds. A caller that shifts traffic between regions can therefore exceed its global limit for up to thirty seconds. That is written in the customer documentation rather than hidden, because a limit nobody can predict is worse than a limit that is slightly loose. **What is stateful, and how it moves** - The counter store is the only stateful component, and its state is disposable by construction - every key has a TTL of two windows. A migration therefore does not migrate: stand up the new cluster, dual-write for two window lengths, cut reads over, drop the old one. Nothing is copied and nothing is lost that was not about to expire anyway. - The policy store is a small relational database with ordinary backups. It is read at process start and refreshed in the background, so it can be offline for a rolling restart without affecting a single decision. - The usage history is on object storage, partitioned by day, and never migrated - old partitions are read by the new reader or expired. **Rollout** - Ship the limiter in SHADOW MODE first: compute the verdict, emit the event, allow everything. Run it for a full week so that the usage history is real before a single customer is throttled. - Read the shadow data and fix the limits. The first set of numbers is always wrong, and finding that out from a dashboard costs nothing while finding it out from a customer costs a great deal. - Enable enforcement per tier, cheapest tier first, one region at a time, with a per-tier kill switch. - Watch the denial rate rather than the error rate. A denial rate that jumps from two percent to forty in one deploy is a bad limit, not an attack, and the two look identical on a latency graph. **Rollback** - Level one, seconds: flip the enforcement flag for a tier back to shadow. No deploy, no restart, and the events keep flowing so that the incident is still observable. - Level two: raise the limits in the policy store. In effect within thirty seconds everywhere. - Level three: the documented kill switch that hard-allows every request. Blast radius is the upstream service, which is exactly the thing the limiter was protecting, so this one has an owner and a written pager expectation rather than being an option somebody discovers. - There is no level four, because the limiter is a library inside the gateway. Rolling it back further means rolling back the gateway, and that is a different runbook. #### Walkthrough 1. **A request arrives** - A caller hits the edge. Nothing has been counted yet, and that matters: the limiter cannot run until the request has an identity, so authentication happens first and floods of unauthenticated traffic belong to the WAF in front of this board. 2. **Decide what is being limited** - The key is the design, not a detail. Limit by IP and a whole office behind one NAT shares a bucket while an attacker holding a block of IPv6 has effectively none. Limit by API key and the numbers finally mean something, at the price of only working for callers you have already identified. 3. **Ask for a token** - The limiter loads the numbers for this tier and route from the policy store rather than from a constant, because the first thing anybody wants during an incident is to raise one customer limit without shipping a deploy. 4. **Token bucket, in two numbers** - A bucket holds 100 tokens and refills 10 a second. The state is only a token count and the time it was last refilled, so refill is arithmetic on read rather than a timer. This is the choice that permits bursts on purpose: a caller who has been quiet for a minute may spend 100 at once, which is what a human clicking looks like. 5. **The boundary a fixed window misses** - A plain fixed window counter lets a caller spend the full limit at 11:59:59 and the full limit again at 12:00:00 - twice the limit inside one second. The sliding window counter keeps the previous window too and weights it by how much is still in view, which removes the double burst for the price of one more integer. 6. **The counter is shared, so it is a write** - Here is the cost of the whole design. Every request must WRITE to a counter that every instance shares, so the limiter adds a network round trip to the traffic it is protecting, and the check and the increment have to be one atomic operation the store runs itself - read then write lets two concurrent requests both see 99 and both get through. 7. **Answer, and say when to come back** - Allowed requests go upstream. Denied ones get a 429 carrying Retry-After and the remaining quota, because a denial that teaches the client nothing is retried immediately and the limiter spends the rest of the incident counting retries of retries. 8. **And when the counter store is down** - Decisions are dropped on a queue nothing waits for, and the review of who got throttled is the only thing that ever tells you the numbers were wrong. The unanswered question is the orange box: when the counter store is unreachable, falling back to per-instance tokens means eighteen gateways each enforce the full limit, and failing closed instead turns a store blip into an outage of a perfectly healthy service. #### Schema - api_keys (The subject a limit applies to. The limiter never reads this table at request time - the key is already in the request.): id uuid PK, owner_id uuid, key_hash char, tier varchar, status varchar, created_at timestamptz - rate_limit_policies (The numbers, as rows. Constants in code would mean a deploy to raise one customer limit during an incident.): id uuid PK, tier varchar, route_class varchar, algorithm varchar, capacity integer, refill_per_sec decimal, window_seconds integer, override_key_id uuid - limit_decisions (60 GB a day. Partitioned by day and dropped after seven, because five years of this is 109 TB of rows nobody queries.): id bigserial PK, api_key_id uuid, route_class varchar, decided_at timestamptz, allowed boolean, remaining integer, region varchar - limit_usage_hourly (The rollup that survives. Only keys that were denied at least once - nobody has ever asked which customer was comfortably under its limit.): id bigserial PK, api_key_id uuid, route_class varchar, hour timestamptz, allowed_count bigint, denied_count bigint, peak_qps integer ### Unique ID generator - 64 bits, and two facts nobody can check - URL: https://tierslate.com/templates/unique-id-generator - Kind: system architecture board; level: starter - Access: paid, 200 AI credits - the question is public, the worked answer is not How 64 bits buy sortable ids with no central counter, and what happens when the clock goes backwards. What is hard about it, left in on purpose: The id is unique only because worker 17 exists exactly once and its clock never moves backwards, and the generator can verify neither. The lease makes the first true only while the registry is reachable, so a worker that keeps generating through a lease expiry - exactly what a network partition produces - mints duplicate primary keys that nothing detects until a UNIQUE constraint fails weeks later on a row nobody is looking at. The clock guard makes the second true by REFUSING TO GENERATE, so a 200 ms NTP correction becomes 200 ms in which every write on the service fails. And because the ids are sortable they are also readable: two ids taken an hour apart tell a competitor exactly how many rows were created in between. #### Start here **Why this design** Every sharded database needs primary keys that are unique without a central counter, and Twitter, Discord and Instagram each answered it with a 64-bit, time-ordered id (Snowflake and its descendants). Interviewers ask it because it looks like a throughput question and is not: one worker covers peak demand twenty-three times over. What it probes is bit budgeting, how clocks really behave under NTP, how a fleet guarantees a worker number exists exactly once, and why a sortable key matters to a B-tree. **What you will learn** - How 64 bits are spent: 1 sign bit, 41 bits of milliseconds (69.7 years from a custom epoch), 5 + 5 bits of datacenter and worker (1,024 generators), 12 bits of sequence (4,096 ids per ms per worker). - Why demand of 174,000 ids/s against a supply of 4.2 billion makes the design coordination bound, and why the real ceiling is 1,024 worker ids - and that the sequence bits are the ones to spend when it runs out. - Why the worker id is a 30 s lease renewed every 10 s rather than a config value, and why a worker past its lease must stop generating instead of carrying on. - What the clock guard does when NTP steps the clock backwards - wait out a few milliseconds, refuse beyond that - and what that costs in failed writes. - Why a sortable bigint beats UUIDv4 at 5 billion ids a day: right-edge B-tree appends against ~97% random reads, and 365 TB against 1.6 PB of index over five years. - When UUIDv4 or auto-increment is the better answer, and why a sortable id needs a separate opaque public token. **How to read this board** - Diagram, left to right: THE CALLER, then THE GENERATOR (the in-process library, the worker id registry and the custom epoch), then SIXTY-FOUR BITS, LAID OUT TO SCALE across the top - the box widths are the bit counts. - Below the bits: WHERE IT GOES WRONG · THE CLOCK, then WHAT IT IS FOR, then THE ALTERNATIVES along the bottom. Press play for the eight-step walkthrough. - Page 1 · Requirements - the questions that decide whether you need any of this, starting with "must the id sort by time?". - Pages 2 and 3 · Capacity and Storage estimation - demand against supply (0.004% utilised), and what an 8-byte sequential key saves in every index. - Page 4 · Availability - five nines because it is a library and not a service, and what degrades when the registry or the clock misbehaves. - Page 5 · How it works - the whole path in prose - then Page 6 · Deployment plan, including why the bit layout is a row and why there is no rollback for a duplicate. - The DATA MODEL at the bottom: bit layouts, worker leases, clock incidents and public tokens - under a megabyte, and the only part that can fail. - Then the conversation to have: the id is unique only while worker 17 exists once and its clock never runs backwards, and the generator can verify neither. A partitioned worker that keeps generating past its lease mints duplicates nothing detects. #### 1 · Requirements **Ask these before drawing anything** - Must the id be sortable by creation time? This is the question that decides everything. If not, UUIDv4 is a better answer than anything on this board and the interview is over in one minute - which is a good answer, not a failure. - Is the id ever shown to a user or put in a URL? Sortable ids are enumerable and they leak volume: two ids an hour apart tell anybody how many rows were created in between. If it is public, it needs a separate opaque external id. - How many bits are available? 64 fits in a bigint and in a JavaScript BigInt but NOT in a JavaScript Number, which silently loses precision above 2^53 and is the most common production bug in this design. - How many generating processes will there ever be? The worker id space is a hard ceiling. 1,024 sounds infinite until a fleet autoscales. - Is the clock trustworthy, and who owns NTP? If nobody can answer this, the design has an unowned dependency at its centre. - What should happen when an id cannot safely be generated - fail the write, or block? There is no third option, and the answer is an availability number. **Functional** - Generate a 64-bit id that is unique across the entire fleet, forever. - Ids increase monotonically with time, so that ordering by id orders by creation. - Generation is in process: no network call, no service to be unavailable. - A worker acquires its worker id automatically on start and releases it on stop. - An id can be decoded back into its timestamp, datacenter and worker, for debugging. **Non-functional** - p99 under 1 microsecond per id. This is arithmetic and a lock, not a request. - Zero duplicates. Not "rare" duplicates - a duplicate here is a primary key collision discovered weeks later in a table nobody was watching. - Survive the id registry being unavailable for the length of a lease without producing a duplicate. Note what this does NOT say: it does not say keep generating. - Ids remain unique across a datacenter failover, a process restart within the same millisecond, and a clock correction. - The scheme must last 69 years from its epoch, and the expiry date must be written down somewhere a human will find it. **Out of scope** - Global ordering. Two ids generated in the same millisecond on two workers have no meaningful order between them, and any code that assumes one is wrong. - Cryptographic unpredictability. These ids are guessable by construction. Anything needing unguessability needs a random token instead, alongside. - Ids for entities that already have a natural key. The best id generator is the one you did not need. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps; a schema of 4 tables and 28 columns. ### Web crawler - a frontier, and the trap of crawling forever - URL: https://tierslate.com/templates/web-crawler - Kind: system architecture board; level: core - Access: paid, 400 AI credits - the question is public, the worked answer is not Why a crawler that feeds on its own output needs politeness, deduplication and loop brakes more than speed. What is hard about it, left in on purpose: The seen set is a Bloom filter, so it is wrong in exactly one direction: a false positive says a page has already been crawled when it has not, the page is dropped, and NOTHING LOGS IT - at one percent over five billion URLs that is fifty million pages silently missing from the corpus and no alert that could fire. Politeness pulls the other way: one request per host per second is correct behaviour and it means a site with ten million pages takes a hundred and fifteen days, so the freshness target and the politeness rule cannot both be met and neither is negotiable. And the trap detector is pure heuristic - depth caps and pattern matching - so a faceted listing with a hundred million URL permutations still burns real crawl budget before anybody notices. #### Start here **Why this design** A crawler is the classic interview system whose output is its own input: every page fetched produces more work, so the hard parts are brakes rather than speed. It probes whether you can make politeness a structural property of the queue, deduplicate at two different levels, and size a probabilistic structure while saying out loud what it gets wrong. Search engines, web archives, price monitors and training-data pipelines all run this loop, and the ones that get it wrong are blocked at the edge or drown in crawler traps. **What you will learn** - Why the frontier is per-host queues under a priority selector, and why one request per host per second makes a 10-million-page site take 115 days against a 30-day refresh. - Sizing: a billion pages a month is 386 fetches/s on average and 772 at peak, about 4,000 sockets across 20 IO-bound fetchers, with parsing on a separate CPU-bound fleet. - URL dedup against content dedup: normalise every link before comparing it, then SHA-256 the stripped body so twelve URLs store one page. - The Bloom filter trade: 9 GB in RAM instead of a 230 GB exact set, paid for with 5 million pages silently never crawled at 0.1% false positives. - Why the frontier is partitioned by HOST and each host owned by one region, so politeness survives three fetch regions. - Why a bad normaliser cannot be rolled back with the code, and how the exact urls table makes the filter rebuildable in about forty minutes. **How to read this board** - Diagram, following the loop: START · ONCE, then THE FRONTIER (queue, politeness scheduler, trap detector), then FETCH with POLITENESS beneath it. - Then EXTRACT and DEDUP on the right, OUTPUT · OFF THE LOOP below them, and the long wires back from the seen set through the trap detector to the frontier - that return is the system. Press play for the eight-step walkthrough. - Page 1 · Requirements - the questions that change the design: how many pages, how fresh, and whether JavaScript must be rendered. - Pages 2 and 3 · Capacity and Storage estimation - the politeness arithmetic that breaks the freshness target, and the Bloom filter sizing. - Page 4 · Availability - why 99% is the right target for a batch system, and which state cannot be rebuilt. - Page 5 · How it works - the loop in prose - then Page 6 · Deployment plan: host ownership by region, shadowed normaliser rollouts, and the rollback that does not undo. - The DATA MODEL at the bottom: hosts, urls, pages and links - the exact record behind the probabilistic one. - Then the conversation to have: the seen set can only be wrong in the direction nothing notices, and politeness and freshness cannot both be met for the largest sites. #### 1 · Requirements **Ask these before drawing anything** - How many pages, and how fresh? A billion pages refreshed monthly and a hundred million refreshed hourly are different systems, not the same system with a different number in it. - Whose web is it - the open web, one customer site, or a set of known domains? A crawl of known domains can negotiate rate limits and skip almost everything on this board. - Is JavaScript rendering required? A headless browser is roughly fifty times the CPU of an HTTP fetch, and that single answer moves the fleet from twenty machines to a thousand. - What counts as a duplicate: the same URL, the same bytes, or the same article on two domains? Each needs a different mechanism and they are often all three. - What is the politeness policy, and who owns the relationship when a site complains? There will be complaints. - Is the crawl allowed to be stopped and restarted, or must it be continuous? That decides whether the frontier has to be durable. - What happens to content that disappears - is a 404 a deletion from the corpus or an outage to retry? **Functional** - Crawl one billion pages and revisit each within thirty days. - Obey robots.txt, including crawl-delay, and cache it per host. - Never fetch the same URL twice within a refresh cycle, and never store the same content twice regardless of how many URLs served it. - Discover new URLs from fetched pages and feed them back into the frontier. - Prioritise: a news homepage is worth more, more often, than a ten-year-old forum thread. - Survive a restart without losing the frontier or the seen set. **Non-functional** - Politeness is a hard constraint, not a target: at most one in-flight request per host, and honour crawl-delay when it is longer than the default one second. - Throughput of 400 pages a second sustained, 800 at peak. - The crawler may be down for an hour without anybody caring. It is a batch system - it degrades by crawling more slowly, which is a completely different availability story from anything serving user requests. - The frontier and the seen set are the only components whose loss is expensive, and they must be durable. - Identify honestly in the User-Agent, with a URL explaining the crawler and a contact address. **Out of scope** - Ranking and the index itself. This system fills a queue; somebody else reads it. - Paywalled or authenticated content. - Rendering JavaScript, in version one. The number is in the capacity page so that the decision is costed rather than assumed. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps; a schema of 4 tables and 30 columns. ### Pastebin - a blob store with a short name - URL: https://tierslate.com/templates/pastebin - Kind: system architecture board; level: starter - Access: free - everything below is the whole board Why storing text is the easy part and expiry, abuse and the read path are the design. What is hard about it, left in on purpose: Expiry is a sweeper that deletes rows in batches every few minutes, and the CDN caches each paste for an hour, so a paste marked "expires in 10 minutes" can stay readable for well over an hour after it should have died - only the origin checks expires_at on every read. Burn-after-reading cannot be cached at all and still goes through the same cached path. The abuse scanner runs off a queue, so a malware paste is served from the moment it is created until the scan finishes, and a takedown only reaches the edge if somebody remembers to purge it. And the text and its row are written to two stores with no transaction between them, so a crash in between leaves orphaned blobs or rows pointing at nothing. #### Start here **Why this design** Pastebin is a common early interview question because it looks like a URL shortener with a bigger value, and the interesting answer is the part that is not. Storing text is trivial: put it in an object store, keep a small row about it, hand back an id. The design is in what happens next - pastes expire, pastes are abused, and pastes are read from caches that do not know either of those things. The same shape appears in gists, shared snippets, crash-report uploads, temporary file shares and "share this config" links. **What you will learn** - Why the text goes in an object store and only ~300 bytes of metadata go in the database - and what that split costs. - How to size it: 2M pastes a day is only ~23 writes/s, but 16 GB of new text a day and ~2,300 reads/s at peak. - Why ids are random 8 character base62 strings (62^8 ≈ 2.2e14) rather than a counter: unlisted pastes must not be guessable. - Lazy expiry on read against an expiry sweeper - and why you need both. - Why immutable text is easy to cache and why that same cache defeats expiry, burn-after-reading and takedowns. **How to read this board** - WRITE PATH across the top: author, client, the Paste API hex, the scan queue and the abuse scanner. - STORES on the right: paste metadata (the row), paste text (the blob) and the hot paste cache. - READ PATH across the bottom: reader, CDN edge, the read service hex, and the response going back. - TAKING IT AWAY on the right: the red expiry sweeper and moderation, the two things that try to make a paste unreadable. - Press play for the walkthrough, then read pages 1 to 6, then the DATA MODEL tables under the pages. - The conversation to have: every arrow that removes a paste is slower than the arrow that serves it. Decide which promise - expiry, burn-after-reading or takedown - you would fix first, and what it costs the cache. #### 1 · Requirements **Functional** - Create: POST text (up to 512 KB) with an optional title, syntax language, visibility (public or unlisted) and expiry (10 minutes, 1 hour, 1 day, 1 week, never, or burn after reading). Returns https://paste.example/{id}. - Read: GET /{id} returns the paste rendered with syntax highlighting; GET /raw/{id} returns text/plain. - Anonymous pastes are allowed; signed-in users can list and delete their own. - An expired, deleted or taken-down paste returns 404 (or 410 Gone) - never the old text. - Anyone can report a paste; moderators can take it down. **Non-functional** - Read latency: p99 under 100 ms for a cached paste, under 300 ms for one fetched from the object store. - Read availability 99.95 percent; create availability 99.9 percent. - Durability: a paste that was acknowledged is never lost before its expiry (object stores give 11 nines). - Unlisted ids must be unguessable: no sequential ids, and no listing endpoint that reveals them. - Expiry is a promise: an expired paste should be unreadable everywhere within a stated bound (say 5 minutes). - Abuse controls: per-IP create rate limits, a size cap, and scanning for credentials and malware. **Out of scope** - Editing a paste in place. A new version is a new paste; immutability is what makes the cache simple. - Full-text search across public pastes - a separate indexing pipeline, and an abuse magnet of its own. - Collaboration, comments and forks. #### 2 · Capacity estimation **Assumptions** New pastes per day 2,000,000 Average paste size 8 KB (cap 512 KB) Reads per paste, lifetime 20 Reads per day 2M x 20 = 40,000,000 Peak factor writes x 3, reads x 5 **Requests per second** Writes 2M / 86,400 s = 23 /s average x 3 = ~70 /s peak Reads 40M / 86,400 s = 463 /s average x 5 = ~2,300 /s peak One paste linked from a big forum thread = 5,000 /s on ONE id These are small numbers. A single modest database handles 70 inserts a second without noticing, and the read rate is well within one cache node. The numbers that matter are bytes, not requests - and the one viral paste, which is why there is a CDN at all. **Bandwidth** Ingress 23 /s x 8 KB = 184 KB/s average Egress 463 /s x 8 KB = 3.7 MB/s average 2,300 /s x 8 KB = 18.4 MB/s peak (~150 Mbit/s) gzip on text, ~3:1 = ~6 MB/s peak on the wire **Cache sizing** Most reads land in the first 24 hours of a paste Pastes created per day 2M x 8 KB = 16 GB Cache the hottest 20 percent = ~3.2 GB A few gigabytes of hot text fits in one cache node with room to spare. It is replicated for availability, not for size. #### 3 · Storage estimation **Two things per paste** Metadata row (database) id 8 B, owner 16 B, blob key ~40 B, size, sha256 32 B, syntax, visibility, flags, created/expires 16 B ~150 B of data + index and row overhead ~300 B per paste Text (object store) 8 KB average **Growth** Text per day 2M x 8 KB = 16 GB Text per year 16 GB x 365 = 5.8 TB written Never-expire share 40 percent Retained per year 5.8 TB x 40% = 2.3 TB Metadata per year 730M x 300 B = 219 GB written Retained per year x 40% = 88 GB This is why the text does not go in the database. Twenty-three terabytes of text after a decade is cheap in an object store (a few hundred dollars a month) and painful in a relational database, where it would bloat every backup and replica. The metadata, under a terabyte after ten years, fits one well-indexed Postgres primary with replicas. **Id space** Alphabet [0-9a-zA-Z] = 62 Length 8 62^8 = 2.2e14 10 years of pastes = 7.3e9 Share of the space used = 0.003 percent Chance a random guess hits a paste = 1 in 30,000 Random ids are generated by the API and inserted with a unique constraint; a collision (vanishingly rare) is simply retried. One in 30,000 is fine against a casual guess but not against a scraper making millions of requests, which is why reads are rate limited per IP too. **Dedup** Storing the SHA-256 of the text lets identical pastes share one blob, which matters for the abuse case: the same credential dump posted ten thousand times is one blob and one takedown. The cost is that a blob can only be deleted once no live row points at it. #### 4 · Availability **What fails, and what it costs** - Object store. Regional, highly available, 11 nines of durability. If it is slow, uncached reads are slow; cached reads continue. - Metadata database. One primary with two replicas. Losing the primary stops creates until failover (30-60 s); reads continue from replicas and cache. - Hot paste cache. Losing it costs latency, never correctness - every miss falls through to the replica and the object store, which at 2,300 reads/s is well within their limits. - CDN. If the edge is down or bypassed, the origin sees every read. Size the read service for the uncached peak. - Scan queue. If it backs up, pastes are served unscanned for longer. Nothing breaks visibly, which is exactly why it needs an alert on queue age. - Expiry sweeper (red). If it stops, nothing is ever deleted - storage grows and, if the read path forgets to check expires_at, expired text is served. **The write is two writes** The API writes the blob first, then the row. If it crashes between them, the blob is an orphan: harmless but billed. Writing the row first would be worse - a row pointing at nothing is a paste that 500s. So: blob first, row second, and a weekly job that lists blobs with no row older than a day and deletes them. **What degrades instead of dying** - Scanner down: pastes go live unscanned and are scanned when it recovers. - Primary down: creates fail fast with a retryable error; reads are unaffected. - Cache down: reads slow from ~5 ms to ~40 ms, nobody sees an error. #### 5 · How it works **Write path** The author submits text. The Paste API checks the size cap and the caller’s rate limit, generates a random 8 character id, gzips the text and PUTs it to the object store under a key derived from its SHA-256. It then inserts the metadata row - id, blob key, size, syntax, visibility and expires_at - and returns the link. Finally it drops a scan job on the queue. The author has their link in about 50 ms; the scan happens after. **Read path** A reader opens the link. The CDN edge serves it if cached. On a miss the read service looks in the hot paste cache, and on a cache miss reads the row from a replica and the text from the object store. The read service checks the row on every origin read: status must be active and expires_at must be in the future, otherwise 404. Text is immutable, so the response carries Cache-Control: public, max-age=3600 - and that header is the whole tension. **Expiry, twice** - Lazy expiry: every origin read compares expires_at to now. This makes the origin correct the instant a paste expires. - The sweeper: every few minutes it deletes rows WHERE expires_at < now() in batches of 10,000 using the index on expires_at, then deletes blobs no live row references. This reclaims space; it is not what makes expiry correct. - The edge: neither of the above touches the CDN. Bound it by setting max-age to min(3600, seconds until expiry), and send Cache-Control: no-store for burn-after-reading. **Burn after reading** The first read must atomically flip the row to burned (UPDATE ... SET status = burned WHERE id = $1 AND status = active RETURNING blob_key) and only the request that wins that update gets the text. It must never be cached anywhere - not at the edge, not in the hot cache - and link-preview bots that fetch URLs pasted into chat will "read" it first unless the page requires a click to reveal. **Abuse** The scanner matches text against credential patterns (emails with passwords, API key formats), known-malware hashes and blocklisted domains, and marks the row flagged. Moderation takes a paste down by setting status, then must purge its URLs from the CDN. A takedown that skips the purge keeps serving for up to an hour. #### 6 · Deployment plan **Topology** - One region is enough for these numbers. Paste API and read service are stateless containers behind a load balancer, at least two per zone across two zones. - Postgres primary with two replicas in different zones; reads go to replicas, with the author’s first read pinned to the primary so a fresh paste is never a 404. - Object store bucket with versioning off and a lifecycle rule as a backstop: objects under the expiring/ prefix older than 8 days are deleted even if the sweeper fails. - A managed CDN in front of the read service only. The API host is never cached. - Sweeper and scanner as separate worker deployments, so a slow scanner never delays deletion. **Rollout** - Read service: canary 5 percent, watch p99, 404 rate and 5xx rate. - Cache-Control changes ship behind a flag and start short: a header sent with max-age=86400 cannot be recalled from caches that already have it. - Id length and alphabet are fixed forever - links are pasted into chat logs and bug trackers for years. **What to watch** Scan queue age (how long pastes are live unscanned), sweeper lag (oldest row past its expires_at), orphan blob count, CDN hit ratio, and create rate per IP - a sudden spike from a handful of addresses is somebody using you as free hosting. #### Walkthrough 1. **Somebody pastes text** - An author pastes a stack trace into the form and picks "expires in 1 day". Up to half a megabyte of text, and an expiry that is a promise. 2. **The API checks and names it** - The Paste API checks the size cap and the caller’s rate limit, then generates a random 8 character id. Random, not a counter, because an unlisted paste is only private for as long as nobody can guess its id. 3. **Text to the blob store, row to the database** - The text goes to the object store, gzipped, under its SHA-256. Then a 300 byte row goes into Postgres with the id, the blob key and expires_at. Two writes, no transaction: blob first, so a crash leaves a harmless orphan instead of a row pointing at nothing. 4. **Scanned afterwards** - The author already has the link. Only now does a scan job reach the abuse scanner, which looks for credential dumps and malware. Until it finishes, whatever was pasted is live. 5. **A reader opens the link** - The link gets shared. The first request reaches the CDN edge; for a paste somebody posted on a busy forum, almost all of the following requests stop here. 6. **The origin checks expiry** - On an edge miss the read service tries the hot cache, then the row and the blob. It checks status and expires_at on every read, so the origin never serves an expired paste. 7. **Served, and cached for an hour** - The text goes back with max-age=3600, because text never changes. Now the edge answers for this paste by itself - and it does not know what expires_at is. 8. **Taking it away is slower** - Every few minutes the sweeper deletes expired rows and their blobs, and moderation takes down what the scanner flagged. Neither reaches the copy at the edge unless somebody purges it, so an expired or taken-down paste keeps being served until the cache entry runs out. #### Schema - users (Only signed-in authors. Anonymous pastes have no owner.): id uuid PK, email varchar, created_at timestamptz, banned boolean - pastes (One row per paste. The text itself lives in the object store under blob_key; this row is what the read service checks on every origin read.): id varchar PK, owner_id uuid, blob_key text, content_sha256 bytea, size_bytes integer, syntax varchar, visibility varchar, burn_after_read boolean, status varchar, created_at timestamptz, expires_at timestamptz - abuse_reports (What moderation works from. Deleted with the paste when the sweeper removes it (ON DELETE CASCADE), so anything needed for an audit trail is copied out at resolution time.): id bigserial PK, paste_id varchar, reason varchar, source varchar, created_at timestamptz, resolved_at timestamptz ### Load balancer - spreading traffic, and noticing who is dead - URL: https://tierslate.com/templates/load-balancer - Kind: system architecture board; level: starter - Access: free - everything below is the whole board How requests are spread across servers, and why the health check matters more than the algorithm. What is hard about it, left in on purpose: The health check runs every 5 seconds and ejects a server after 3 failures, so a server that crashes keeps getting its share of requests for up to 15 seconds - about 18,000 failed requests at this board's peak. It checks /healthz, which only proves the process answers: a server whose database pool is exhausted passes every check while returning 500s to real users. Making the check deep (query the database) fixes that and creates the opposite failure - one database blip fails every server's check at once and the balancer ejects the whole pool. Sticky sessions pin users to one server, so removing it logs them out, and the balancer pair is active/standby on one floating IP, so a failover drops every open connection. #### Start here **Why this design** A load balancer appears in nearly every system design answer, usually as one unexplained box. Interviewers ask about it directly to see whether you know what is inside: how a request is assigned to a server, how the balancer learns that a server is gone, what happens to the requests in flight when it does, and what happens when the balancer itself fails. In real systems this is an AWS ALB or NLB, an NGINX or HAProxy pair, Envoy, or a Kubernetes Service - the same decisions with different defaults. **What you will learn** - Layer 4 against layer 7: forwarding TCP connections versus reading HTTP requests, and what each can and cannot do. - Round robin, least connections and consistent hashing - and why the choice matters less than people think. - Health checks as numbers: interval x threshold = how long a dead server keeps receiving traffic. - Shallow against deep checks, and how a deep check can take down a healthy pool. - Connection draining, sticky sessions, and keeping the balancer itself from being the single point of failure. **How to read this board** - FRONT DOOR on the left: the client, DNS, the active balancer hex, its standby, and the red health checker. - SERVER POOL in the middle: four app servers, one draining and one dead, plus the autoscaler that adds and removes them. - SHARED STATE on the right: the session store and the database every server depends on. - Press play for the walkthrough, then read pages 1 to 6, then the DATA MODEL under the pages. - The conversation to have is the red box. Pick the interval, the threshold and what the check tests, and say what each choice costs when a server dies and when the database blinks. #### 1 · Requirements **Functional** - Accept HTTPS on one public name (api.example.com) and terminate TLS. - Spread requests across a pool of identical app servers. - Detect unhealthy servers and stop sending them traffic; bring them back when they recover. - Add and remove servers without dropping requests (deploys and autoscaling). - Route by path or host when needed (/api to one pool, /static to another). - Optionally keep a client on the same server (session affinity). **Non-functional** - Added latency under 1 ms at p99 for the balancer itself. - Availability 99.99 percent for the front door - higher than any single server behind it. - A dead server should stop receiving traffic within seconds, not minutes. - No requests dropped during a planned removal (deploy, scale-in). - Handle 20,000 requests/s and 200,000 concurrent keep-alive connections. **Out of scope** - Global traffic steering across regions (GeoDNS, anycast) - one level up from this board. - Rate limiting and authentication, which belong to an API gateway. - Service-to-service balancing inside a mesh, which is the same ideas run in every client. #### 2 · Capacity estimation **Traffic** Average requests per second 6,000 Peak requests per second 20,000 Average response size 20 KB Concurrent keep-alive connections 200,000 New TLS connections per second 2,000 (peak) **How many servers** One app server, comfortable 1,500 req/s Peak / per server 20,000 / 1,500 = 13.3 -> 14 Plus headroom to lose a zone (N+2) = 16 servers At peak, each server takes 20,000 / 16 = 1,250 req/s The board draws four servers to stay readable; the numbers assume sixteen spread over two zones. The extra two are not waste: they are what lets one server die, or one be drained for a deploy, without the rest going over their comfortable load. **Bandwidth through the balancer** Egress 20,000 x 20 KB = 400 MB/s (~3.2 Gbit/s) peak 6,000 x 20 KB = 120 MB/s average Ingress requests ~2 KB = 40 MB/s peak A layer 7 balancer has to decrypt and re-read all of this. 3.2 Gbit/s plus 2,000 TLS handshakes a second is within one modern balancer instance, but not with much room - which is why managed balancers scale out behind the scenes and why a self-run pair is sized for twice the peak. **The health check window** Interval 5 s x unhealthy threshold 3 = up to 15 s Requests sent to a dead server in that window 1,250 req/s x 15 s = 18,750 Interval 2 s x threshold 2 = 4 s -> 5,000 Check traffic: 2 LBs x 16 targets / 5 s = 6.4 checks/s Health checks cost almost nothing, so the temptation is to make them very frequent. The limit is not load, it is flapping: a check every second with a threshold of one ejects a server for a single slow garbage-collection pause. #### 3 · Storage estimation A load balancer stores very little, and that is a design goal: anything it keeps is something a standby has to have too. **In the balancer** Connection table 200,000 x ~300 B = ~60 MB Config (pools, targets, rules, certs) = < 1 MB Health state 16 targets x a few bytes = nothing The connection table is the one piece of state that matters, and it is not replicated to the standby in this design. That is why a failover drops every open connection: the standby has never heard of them. **Behind the balancer** Active sessions 2,000,000 Per session (Redis) ~1 KB Session store = ~2 GB Two gigabytes fits one Redis primary with a replica. Moving sessions out of server memory into this store is what makes sticky sessions optional: any server can serve any user, so removing one logs nobody out. **Access logs** Requests per day 6,000 x 86,400 = 518,000,000 Per log line ~400 B Per day = ~207 GB Keep 30 days = ~6.2 TB The balancer's access log is the best single source of truth for latency and error rates, because it sees every request including the ones that never reached a server. Ship it to object storage, not to local disk. #### 4 · Availability **A dead server** - Active checks: the balancer calls GET /healthz on every target every 5 s. Three failures in a row eject it; two passes bring it back. - Passive checks (outlier detection): the balancer also watches real traffic. Five 5xx responses or connection resets in a row eject a target immediately, without waiting for the next active check. This closes most of the 15 second window. - Retries: a request that fails to CONNECT can safely be retried on another server. A request that was sent and timed out can only be retried if it is idempotent (GET, or a POST with an idempotency key). **What the check should test** - Shallow (process answers): never ejects the pool by mistake, but misses a server that is up and broken. - Deep (database reachable): catches the broken server, but when the database blips, every server fails together and the balancer ejects all of them. - The usual answer: check what is local to the server (process, thread pool, disk, its own connection pool has free slots), not shared dependencies. And set a panic threshold: if more than half the pool looks unhealthy, ignore health and send to everyone, because the checker is more likely wrong than the pool. **A dead balancer** - Active/standby on a floating IP (VRRP / keepalived): the standby takes the IP within 1-3 s. Every established connection is lost because the connection table was not shared. - Active/active behind DNS or anycast, or a managed balancer that spreads itself across zones: no single balancer is the front door. This is the better answer when you can have it. **A planned removal** Draining: the target is marked draining, receives no NEW requests, and finishes the ones it has for up to a drain timeout (30 s here). The deploy waits for drain before stopping the process. Skipping this is the most common reason deploys cause a small spike of errors. #### 5 · How it works **Getting to the balancer** The client resolves api.example.com. DNS returns the balancer's public address - a floating IP held by whichever balancer is active. The client opens a TCP connection and a TLS session to that address; the balancer holds the certificate and terminates TLS. **Layer 4 or layer 7** - Layer 4 (TCP): picks a server when the connection opens and forwards bytes. Very fast, sees no HTTP, so it cannot route by path, retry a request or read a cookie. One long-lived connection carrying many requests all goes to one server. - Layer 7 (HTTP): terminates the connection, reads each request, picks a server per request, and keeps its own pool of connections to the servers. Can route by path and host, add headers, retry, and spread a single client's requests. Costs more CPU per request. - This board is layer 7. **Choosing a server** - Round robin: next server in turn. Fine when requests cost about the same. - Least connections (drawn here): the server with the fewest requests in flight. Better when some requests are slow, because a server stuck on slow requests stops being picked. - Power of two choices: pick two servers at random, send to the less loaded. Nearly as good as least connections and does not need an exact global count - the common choice for balancers that run as many instances. - Consistent hashing on a key: the same user or cache key always lands on the same server. Needed when servers hold local state (caches); otherwise it only makes load uneven. **Sticky sessions** Affinity by cookie keeps a user on one server so in-memory session data stays valid. It also makes load uneven and turns every server removal into a mass logout. The better fix is the one drawn on the right: put sessions in a shared store and turn affinity off. **Adding and removing servers** The autoscaler watches CPU and request rate. When it adds a server, it registers it with the balancer as a new target; the target receives traffic only after it passes two health checks in a row. When it removes one, it deregisters, waits for drain, then terminates. #### 6 · Deployment plan **Topology** - Two balancer nodes in different zones (or one managed balancer that spans them). Health checks on, outlier detection on, panic threshold at 50 percent. - Sixteen app servers across the same two zones, eight per zone, in one target group. Cross-zone balancing on, so losing a zone moves traffic rather than overloading the survivors in one zone. - Redis for sessions with a replica in the other zone; affinity off. - Access logs to object storage; metrics per target (requests, 5xx, latency, health state) to the monitoring system. **Rolling deploy through the balancer** - Pick 2 of the 16 servers (12.5 percent). Mark them draining. - Wait up to 30 s for in-flight requests to finish. - Deploy, start, and wait for two passing health checks before they receive traffic again. - Watch their 5xx rate and latency against the rest for a few minutes, then continue in batches. **Changing the balancer itself** - Config changes (rules, weights, timeouts) are versioned and applied to the standby first, then failed over to, so the active node is never the one being edited. - Certificate rotation: load the new certificate alongside the old one before expiry, never on the day. - Any change to health check settings is tested by killing a server on purpose in staging and timing how long it keeps receiving traffic. **What to watch** Healthy target count per zone, 5xx from the balancer itself (no target available) versus from targets, per-target request share (one server taking far more than 1/16 means affinity or hashing is skewed), surge queue length, and TLS handshake rate. #### Walkthrough 1. **Find the front door** - The client looks up api.example.com. DNS returns one address - a floating IP that belongs to whichever balancer is active right now. 2. **TLS ends at the balancer** - The client opens an HTTPS connection to that address. The balancer holds the certificate, decrypts the request and reads it, which is what lets a layer 7 balancer choose a server per request rather than per connection. 3. **Pick the least busy server** - With least connections, the request goes to the server with the fewest requests in flight. Round robin would also be fine here; on a pool of healthy servers the algorithm barely matters. 4. **Any server can serve anyone** - The server reads the user's session from a shared store instead of its own memory. That is what makes sticky sessions unnecessary - and why taking a server away does not log anybody out. 5. **Drain before you deploy** - Server 3 is being deployed. It is marked draining: it gets no new requests and has 30 seconds to finish the ones it has. Stopping it without draining is how a deploy turns into a burst of errors. 6. **A server dies** - Server 4 has just crashed. The balancer does not know yet, so it keeps sending it one request in four. Every one of those fails. 7. **The check takes up to 15 seconds** - The health checker probes /healthz every 5 seconds and needs 3 failures in a row. That is up to 15 seconds - about 18,000 requests at peak - sent to a dead server. Passive checks on real traffic would catch it sooner. 8. **Eject - and what the check misses** - After the third failure the server is ejected and the rest share its load. But /healthz only proves the process answers: a server that cannot reach the database still passes. Check the database instead, and one database blip ejects every server at once. #### Schema - target_groups (A pool of interchangeable servers and the rules for checking them. The two thresholds and the interval are the board's tension written as data.): id serial PK, name varchar, algorithm varchar, health_path varchar, interval_seconds integer, unhealthy_threshold integer, healthy_threshold integer, drain_timeout_seconds integer - targets (One row per server registered in a group. The autoscaler writes it; the health checker moves its state.): id serial PK, group_id integer, address varchar, port integer, zone varchar, state varchar, registered_at timestamptz - health_results (A short rolling history per target - enough to see a flap, not a permanent log.): id bigserial PK, target_id integer, passed boolean, latency_ms integer, checked_at timestamptz ### API gateway - one front door for many services - URL: https://tierslate.com/templates/api-gateway - Kind: system architecture board; level: starter - Access: free - everything below is the whole board Why auth, routing and limits move to one front door, and what that door costs when it falls over. What is hard about it, left in on purpose: Every request to every service crosses one gateway fleet, so a gateway bug, a memory leak or a bad TLS certificate is an outage of every API at once rather than of one. The route table is pushed to all gateway instances within seconds with no staged rollout, so one bad route (a typo in an upstream, a prefix that shadows another) breaks traffic everywhere before anyone can look. The rate limiter writes one shared Redis on every request and is configured to fail closed, so a Redis failover - normally a 10 second blip nobody notices - becomes a 10 second total outage. And every request pays the gateway hop, a few milliseconds that internal service-to-service calls routed back through the front door pay twice. #### Start here **Why this design** Almost every microservice architecture ends up with an API gateway, and interviewers ask about it to see whether you understand both halves of the trade: what moves into the gateway (TLS, authentication, rate limits, routing, logging) and what that centralisation costs (an extra hop on every request, and a component whose failure is everybody's failure). Real examples are Kong, AWS API Gateway, Apigee, Envoy as an edge proxy, and the NGINX config every company eventually grows into one. **What you will learn** - Which concerns belong in a gateway - and which (business logic, data joins) do not. - How a gateway authenticates without calling the identity provider on every request: JWT signatures checked against cached public keys. - Sizing: 50,000 requests/s at peak needs about 15 gateway instances and 50,000 counter writes/s for rate limiting. - Why fail-open versus fail-closed on the rate limiter is a business decision, not a technical default. - How to ship route changes to a fleet that every API depends on without breaking everything at once. **How to read this board** - CALLERS on the left: the app user with a token, the partner with an API key. - GATEWAY PIPELINE across the top: edge (TLS and WAF), authenticate, rate limit, then the router hex. - GATEWAY STATE underneath: the identity provider's public keys, the red rate-limit counters, the route table and the control plane that publishes it. - SERVICES on the right: three of the twelve backends, and the access log stream. - Press play, read pages 1 to 6, then the DATA MODEL tables. - The conversation to have: name every way this one fleet can take down all twelve services, and for each one say what you would change. #### 1 · Requirements **Functional** - Expose all public APIs under one hostname, api.example.com, routed by path prefix (/v1/users, /v1/orders, /v1/catalog ...) to 12 backend services. - Authenticate every request: end-user JWTs (from the company login) and partner API keys. - Enforce rate limits per API key and per user according to a plan (free 60/min, pro 600/min, partner 6,000/min). - Pass the verified identity to services in trusted headers (X-User-Id, X-Client-Id), stripping any the caller sent. - Log every request with status, latency, caller and route. - Let service teams add or change their routes without a gateway deploy. **Non-functional** - Added latency: p99 under 5 ms for the gateway itself. - Availability 99.99 percent - at least as high as the best service behind it, since it is in front of all of them. - A route change reaches all instances within 30 seconds and can be rolled back as fast. - Peak 50,000 requests/s, scaling horizontally with no shared state on the request path except the rate counters. **Out of scope** - Response aggregation and business logic. A "backend for frontend" that stitches three calls into one is its own service behind the gateway, not part of it. - Service-to-service traffic inside the network, which should not go back out through the front door. - Issuing tokens - that is the login service; the gateway only verifies them. #### 2 · Capacity estimation **Traffic** Average requests/s 15,000 Peak requests/s 50,000 Backend services 12 Average request + response 6 KB **Gateway fleet** One instance (4 vCPU), comfortable 5,000 req/s Peak / instance 50,000 / 5,000 = 10 Plus 50% headroom, 3 zones = 15 instances **Where the gateway spends its time** TLS termination (on resumed sessions) ~0.2 ms JWT verify, RS256, key already cached ~0.05 ms Rate limit: one Redis round trip ~0.5 ms Route match + header rewrite ~0.02 ms Proxy to backend (connection pooled) ~0.5 ms --------- Gateway overhead ~1.3 ms typical Most of the budget is the two network round trips - to the counter and to the backend. Verifying a JWT is cheap because it is pure CPU on a public key the gateway already holds; calling the identity provider to introspect each token instead would add a round trip and make the identity provider take 50,000 requests a second. **Rate limit counters** One INCR + EXPIRE per request = 50,000 writes/s peak One Redis primary, comfortable ~ 100,000 ops/s Utilisation at peak ~ 50 percent Half of one Redis is fine today and a problem at twice the traffic. The fix is to shard counters by key across a Redis cluster, or to have each instance keep a local count and sync every 100 ms, accepting that the limit is approximate. **Bandwidth** 50,000 x 6 KB = 300 MB/s (~2.4 Gbit/s) peak, through the fleet Per instance = ~20 MB/s at 15 instances #### 3 · Storage estimation A gateway holds little data of its own, and almost all of it is configuration that is read on every request and written rarely. **Configuration** Routes ~300 rows x ~500 B = 150 KB API clients 50,000 x ~200 B = 10 MB API keys (hashed) 80,000 x ~150 B = 12 MB Plans a handful All of it fits in memory in every gateway instance. The database is the source of truth; instances load a snapshot at startup and receive changes by push, so a database outage stops config changes but not traffic. **Rate limit counters** Active callers in any minute ~2,000,000 One counter per caller per window ~60 B Live counters = ~120 MB Counters expire with their window, so the store never grows; it only needs memory for one or two windows of active callers. **Access logs** Requests per day 15,000 x 86,400 = 1.3 billion Per log line ~500 B Per day = ~650 GB Compressed in object storage (~8:1) = ~80 GB/day Keep 90 days = ~7 TB #### 4 · Availability **The gateway is everybody's dependency** If each of 12 services is 99.95 percent available, a client using all of them sees roughly their product. The gateway multiplies into every one of those: a 99.9 percent gateway caps every API at 99.9 percent. So the gateway fleet is run like the most critical service in the company - spread over three zones, autoscaled, with its own on-call. **What fails, and what it costs** - One gateway instance: the load balancer in front ejects it; the other 14 absorb its share. - A bad gateway release: every API breaks at once. Canary it on 1 instance (about 7 percent of traffic) and watch error rates per route before going further. - A bad route config: pushed everywhere in seconds. Validate before publish (upstream exists, no prefix shadows another), then roll out to a canary group first. - Rate limit Redis (red): fail closed and a failover is a total outage; fail open and abusive clients go unlimited for its duration. The common answer is fail open with a local per-instance limit as a backstop, so nothing is completely unprotected. - Identity provider: tokens are verified locally against cached keys, so an outage only stops NEW logins, not existing traffic - as long as the key cache is kept, not dropped, when a refresh fails. - A slow backend: without per-route timeouts and connection limits, one slow service ties up gateway workers and starts failing everybody else's requests. Each route gets its own timeout and its own connection pool (a bulkhead). **What degrades instead of dying** - Control plane down: no route changes, traffic unaffected. - Log stream backed up: logs buffer locally then drop; requests are never blocked on logging. - Counters down: limits fall back to per-instance approximations. #### 5 · How it works **One request, stage by stage** - Edge: TLS is terminated and a web application firewall rejects obviously bad requests (oversized bodies, known attack patterns) before any work is done. - Authenticate: for a Bearer token, check the JWT signature with the identity provider's public key (JWKS, cached for 10 minutes), then the expiry and audience. For an API key, hash it and look it up in memory. Failure is 401. Success attaches the caller's id and plan. - Rate limit: increment the counter for (caller, current minute) in Redis and compare with the plan's limit. Over the limit is 429 with a Retry-After header. - Route: match the longest path prefix in the route table, strip identity headers the caller sent, add the verified ones, and proxy to that service over a pooled connection with the route's timeout. - Log: emit one access log line to the stream, without waiting. **Why verify tokens locally** A JWT is signed by the identity provider. Anyone with the public key can check the signature, so the gateway never needs to call the identity provider on the request path. The cost is revocation: a stolen token stays valid until it expires, which is why access tokens are kept short (5 to 15 minutes). The login-and-sessions board is about exactly that trade. **How routes reach the fleet** A service team edits its routes through the control plane. The control plane validates them, writes a new version of the route table and pushes it to every gateway instance, which swaps it in atomically between requests. Every instance reports which version it runs, so a rollout can be watched and a bad version rolled back by pushing the previous one. **What stays out** - Business rules: "free users cannot export" belongs in the service. The gateway knows plans for rate limits, not features. - Joining data from several services: that is a separate aggregation service. - Per-service special cases accumulated as plugins: each one is code every request runs, owned by a team that does not run the gateway. #### 6 · Deployment plan **Topology** - A network load balancer in front, spreading TCP across 15 gateway instances in 3 zones (5 per zone). - Gateway instances are stateless: config in memory, counters in Redis, logs streamed out. - Redis cluster for counters, with replicas in a second zone. - Control plane and its Postgres in one zone - it is not on the request path. - Backends are reached over the private network; they accept traffic only from the gateway's security group, so nobody can skip authentication by calling them directly. **Releasing the gateway** - Deploy to 1 instance. Compare its 5xx rate, p99 latency and per-route error rates with the others for 15 minutes. - Then one zone (a third of traffic), then the rest. - Automatic rollback if 5xx rises above baseline by more than 0.5 percentage points. **Releasing a route change** - Validate: upstream resolves, timeout set, no existing route shadowed. - Push to a canary group of 2 instances; watch that route's error rate. - Push to all. Keep the previous version one click away. **What to watch** Gateway-added latency (total minus upstream time), 5xx split into gateway-generated and upstream-generated, 401 and 429 rates per route, counter store latency, config version per instance, and open connections per upstream - a route whose connections climb is the slow service about to drag everybody down. #### Walkthrough 1. **One hostname for everything** - The app and the partner both call api.example.com. Neither knows there are twelve services behind it. TLS ends at the edge and the firewall turns away obvious junk before any real work is done. 2. **Who is calling?** - The gateway checks the JWT signature against the identity provider's public key, which it fetched minutes ago and cached. No call to the identity provider per request - that is what makes this stage cost microseconds instead of a round trip. 3. **Under the limit?** - The rate limit stage increments the caller's counter for this minute in Redis and compares it with their plan. This is the one store every request writes - 50,000 times a second at peak - and it is set to fail closed. 4. **Find the service** - The router matches the longest path prefix in its route table, removes any identity headers the caller tried to set, and adds the verified ones. 5. **The service just does its job** - The orders service receives a request that is already authenticated, already under its limit, and already carries X-User-Id. It contains none of that code - which was the point of the gateway. 6. **Logged, without waiting** - One access log line goes on the stream. If the stream backs up, the line is dropped, never the request. 7. **A team changes a route** - A service team publishes route table v413 through the control plane. It is validated and written - and then pushed to every gateway instance within seconds. 8. **One door, one blast radius** - That push reaches every instance at once, with no canary. One bad route, one gateway bug or one Redis failover with fail-closed limits, and every API behind this door fails together. That is the price of having one door. #### Schema - plans (What a caller is allowed. The gateway reads limits from here; feature rules stay in the services.): id serial PK, name varchar, requests_per_minute integer, burst integer, fail_open boolean - api_clients (A partner integration or first-party app.): id uuid PK, name varchar, plan_id integer, contact_email varchar, created_at timestamptz - api_keys (Only the hash is stored. The prefix is shown in the dashboard so a customer can tell their keys apart.): id uuid PK, client_id uuid, key_prefix varchar, key_hash bytea, scopes text, created_at timestamptz, revoked_at timestamptz - routes (The route table the control plane publishes. Every gateway instance holds all of it in memory.): id serial PK, path_prefix varchar, upstream varchar, timeout_ms integer, max_connections integer, auth_required boolean, owner_team varchar, config_version integer, updated_at timestamptz ### Login and sessions - who you are, on every request - URL: https://tierslate.com/templates/login-sessions - Kind: system architecture board; level: starter - Access: paid, 200 AI credits - the question is public, the worked answer is not How a password becomes a session, and what changes when the session is a signed token nobody can revoke. What is hard about it, left in on purpose: Access tokens are JWTs that every service checks on its own with a public key - that is what makes each request cost microseconds, and it is exactly why they cannot be taken back. Logout deletes the session row and its refresh token, but the access token already in the browser keeps working for up to 15 minutes, and so does a stolen one; "change password" and "sign out everywhere" have the same 15 minute hole. Shortening the lifetime moves load onto the refresh endpoint, and adding a denylist puts a shared lookup back on every request, which is the thing JWTs were chosen to avoid. Separately, failed logins are limited per account, so anyone who knows your email can lock you out on purpose. #### Start here **Why this design** Every product with accounts has this system, and most security incidents involve some part of it: weak password storage, credential stuffing, a session that outlives a logout, a token that cannot be revoked. Interviewers use it to check that you know how passwords are stored, the difference between authenticating once and checking a credential on every request, and the real trade between server-side sessions and signed tokens - not the blog-post version where JWTs are simply better. **What you will learn** - How a password is stored (argon2id or bcrypt, with a per-user salt) and why the hash is slow on purpose. - Why rate limiting must happen BEFORE the hash: one credential-stuffing attack can need hundreds of CPU cores otherwise. - Opaque session ids against signed tokens: a lookup on every request against no lookup and no revocation. - The hybrid most systems use: a 15 minute access JWT plus a rotating refresh token kept server side. - Cookies done right: HttpOnly, Secure, SameSite - and what each one stops. **How to read this board** - LOG IN · ONCE across the top: user, browser, the login service hex, the password hasher and the token signer. - STORES on the right: users, failed login attempts, and sessions with their refresh tokens. - EVERY REQUEST across the bottom: the API server hex checking tokens locally, the services behind it, the refresh endpoint and the red logout. - Press play, then read pages 1 to 6, then the DATA MODEL tables. - The conversation to have: what exactly happens to a stolen access token after the user logs out, and what you would pay to close that window. #### 1 · Requirements **Functional** - Sign up with email and password; log in; log out. - Stay logged in across browser restarts for up to 30 days of inactivity. - Every API request identifies the user without asking for the password again. - Several devices at once, each a separate session the user can see and end. - "Sign out everywhere" and password change end all other sessions. - Lock out guessing: limit failed attempts by IP and by account. **Non-functional** - Checking a credential on a request: under 1 ms, and no single shared store on the hot path if it can be avoided. - Login: under 500 ms including the deliberately slow hash. - Passwords are never stored or logged in a recoverable form. - A logout or revocation takes effect within a stated bound - this board says 15 minutes. - Login availability 99.9 percent; request checking 99.99 percent (it is in front of everything). **Out of scope** - Social login and SSO (OAuth / OIDC with another provider) - the same session machinery after a different first step. - MFA flows in detail. The login service is where they plug in. - Authorisation (what a user may do) beyond carrying their id and roles in the token. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps; a schema of 4 tables and 27 columns. ### Feature flags - a config change that ships like code - URL: https://tierslate.com/templates/feature-flags - Kind: system architecture board; level: starter - Access: paid, 200 AI credits - the question is public, the worked answer is not Why a flag is evaluated locally from a pushed ruleset, and what a stale ruleset does during an incident. What is hard about it, left in on purpose: Local evaluation removes the flag service from the request path and replaces one source of truth with 6,000 copies of it, and nothing on this board knows how old any one copy is. A pod whose stream died silently - a half-open TCP connection behind a proxy idle timeout - keeps evaluating ruleset v4170 forever and reports nothing, so the kill switch flipped during an incident reaches 99.7% of the fleet in a second and the last eighteen pods keep serving the broken path until somebody notices by hand. Boot is worse: if the snapshot store is unreachable, a new pod falls back to a last-known file of unknown age or to the defaults compiled into the code, which may be the opposite of what production is running. There is no ruleset-age metric per pod and no maximum staleness after which the SDK refuses to answer, and 720 of the 1,800 flags have been fully rolled out for over ninety days and should not exist. #### Start here **Why this design** Every team that ships continuously ends up with feature flags, and almost every team builds the first version wrong: a database table and an HTTP endpoint that each service calls to ask "is this on?". That works until the flag check is the most-called endpoint in the company. The real design moves the rules to the caller - a compiled ruleset pushed into every process and evaluated in memory - and the interesting questions become the ones about distribution: how fast a change arrives, what a process does when it has not heard from anyone, and how you know. **What you will learn** - Why 16.8 million evaluations a second have to be local, and why the ruleset that makes that possible is about 140 KB compressed. - Why a flag change deserves the lifecycle of a deploy - versioned, audited, compiled, rolled back - and why it gets less review than one. - How a streaming relay plus an immutable snapshot store gets a kill switch to 6,000 pods in about a second, and where the long tail of that second lives. - Why deterministic bucketing (a hash of flag key and user id) is what makes a 10% rollout the same 10% on every pod and every request. - What a stale ruleset does during an incident, and why "how old is your copy?" is the question this board cannot answer. **How to read this board** - Press Play: the walkthrough follows one kill-switch change from the console, through the compiler and relay, into a pod, and out the other side as an exposure event. - AUTHORING (left) is the slow, human half; DISTRIBUTION (middle) is the copy; EVALUATION (right) is the request path with no network in it. - 1 · Requirements, then 2 · Capacity and 3 · Storage for where 16.8M/s and the 140 KB snapshot come from. - 4 · Availability - what each box failing does, and why the flag service may go down without anyone noticing. - 5 · How it works in prose, then 6 · Deployment - including how a flag system is itself rolled out without flags. - Finally the DATA MODEL, then argue with the weakness: the red ruleset in every pod has no age and no expiry. #### 1 · Requirements **Ask these before drawing anything** - Server-side only, or browsers and mobile apps too? A client-side SDK cannot be handed the whole ruleset - it would leak every targeting rule and every unreleased feature name - so it needs a server that evaluates for it. That is a different system. - How fast must a change arrive? "Within a second" for a kill switch is a streaming design; "within a minute" is polling, and polling is far simpler. - What does targeting look like - percentages only, or user attributes, segments, plans, regions? Segments of a million user ids change the snapshot size by orders of magnitude. - Are flags used for experiments? Then evaluation must emit exposure events, and the assignment must be deterministic and sticky. - Who may change a production flag, and does that need approval? A flag flip has taken down more production systems than most deploys. - What should a process do if it has never received a ruleset? Code default, last-known file, or refuse to start - each is wrong somewhere. **Functional** - Create flags with typed variants (boolean, string, number, JSON) and a default per environment. - Target by rules over request context: attribute matches, segment membership, and percentage rollouts by a stable hash. - Evaluate in-process with no network call on the request path. - Propagate a change to every connected process, and record who changed what, when and why. - Emit exposure events for experiment flags, deduplicated. - Roll back any flag to any previous version in one action. **Non-functional** - Evaluation p99 under 5 µs, zero network calls, zero allocation on the hot path. - Change propagation to connected SDKs: p50 300 ms, p99 under 2 s. - The request path must keep working with the entire flag platform down - evaluation degrades to the last ruleset, never to an error. - Every production change is attributable and reversible. - Same user, same flag, same answer on every pod, unless the rules changed. **Out of scope** - Client-side (browser/mobile) evaluation and its proxy. - Experiment statistics - this board stops at the exposure event landing in the warehouse. - Long-lived configuration (timeouts, limits). It is tempting to put it in the flag system, and it turns a release tool into a config database nobody is allowed to clean up. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps; a schema of 6 tables and 37 columns. ### Secrets manager - the one store everything trusts - URL: https://tierslate.com/templates/secrets-manager - Kind: system architecture board; level: starter - Access: paid, 200 AI credits - the question is public, the worked answer is not How secrets are encrypted under a key nobody holds directly, and why rotation is the feature, not storage. What is hard about it, left in on purpose: Everything trusts this store, so everything waits on it: a pod cannot start until it has read its secrets, and the agent's five-minute cache helps pods that are already running and does nothing for a new one. A KMS throttle or a secrets-API outage during an autoscale or a full-fleet restart - 12,000 pods, 72,000 reads in ten minutes - means nothing new comes up, which turns a vault incident into an every-service incident. Rotation is drawn correctly on the vault's side (two database users, alternate which one is changed, stage then test then promote) and not at all on the consumer's: a service that read its password once at boot into a config object keeps working after the first rotation because the other user is untouched, and fails thirty days later when that user's password changes - an outage dated a month after the deploy that caused it. Plaintext also outlives every policy here, cached in agent memory and in environment variables that crash dumps happily print. #### Start here **Why this design** Every company eventually discovers its database passwords in a Git history, a CI log and a Slack message, and buys or builds a secrets manager. The interesting part is that the obvious version - an encrypted table and an API - solves the least important problem. A secrets manager is really three systems: envelope encryption so that holding the data is not holding the secrets, machine identity so that a workload can prove who it is without a password, and rotation so that a leaked credential stops working on a schedule nobody has to remember. **What you will learn** - Envelope encryption: a data key per secret version, wrapped by a key-encryption key that never leaves the HSM - and why that makes a stolen backup worthless. - Why "authenticate with a secret to get your secrets" is a loop, and how platform-issued workload identity breaks it. - How dual-user rotation changes a database password under a live fleet with no outage, step by step. - Why the read rate is set by deploys and cold starts rather than by traffic, and what a 12,000-pod restart does to the KMS quota. - Why the audit log is the actual product for a security team, and why it is larger than everything else combined. **How to read this board** - Press Play: the walkthrough follows a pod booting, proving who it is, and receiving a password decrypted under a key nobody holds - then a rotation changing that password under it. - CALLERS (left) is who is asking; THE VAULT (middle) is ciphertext plus a KMS drawn red; ROTATION (right) is the feature. - 1 · Requirements, 2 · Capacity and 3 · Storage - including why the audit log outweighs the secrets 10,000 to one. - 4 · Availability - why the vault is on the boot path of every service, and how to get it off. - 5 · How it works in prose, then 6 · Deployment - including the root key ceremony nobody wants to think about. - Finally the DATA MODEL, then argue with the weakness: rotation is drawn on the vault's side and not on the consumer's. #### 1 · Requirements **Ask these before drawing anything** - Which secrets are static (an API key a vendor issued) and which can be rotated by us (a database role we own)? Only the second kind can be rotated automatically, and it is usually the minority. - How do workloads identify themselves today - Kubernetes service accounts, cloud IAM roles, VM instance identity, or nothing? The answer decides whether this is a weekend or a year. - Is a secret read once at boot, or on every use? The first is cheap and makes rotation dangerous; the second is safe and puts the vault on the request path. - What compliance regime applies? PCI and SOC 2 set audit retention (a year is common) and require proving who read what - which sizes the largest table here. - Who can read a secret as a human, and how is break-glass access granted and reviewed? - Should the vault issue dynamic credentials (a fresh database user per lease) rather than store static ones? It removes rotation entirely and adds a database write per lease. **Functional** - Store versioned secrets under hierarchical paths (prod/payments/db/orders). - Encrypt every version under its own data key, wrapped by a KMS key-encryption key. - Authenticate workloads by platform-issued identity; authorise by policy over identity and path. - Rotate rotatable secrets on a schedule through stages: pending, current, previous. - Log every read, write, denial and rotation step, immutable. - Soft-delete with a recovery window; never hard-delete a version still referenced. **Non-functional** - Read p99 under 30 ms including the KMS unwrap. - Availability 99.99% - it is on the boot path of every service. - A full dump of the database plus every backup must not reveal a single plaintext. - No plaintext at rest anywhere the vault controls; plaintext in transit only over mTLS. - Rotation of a dual-user secret completes with zero failed authentications on consumers that re-read. **Out of scope** - Certificate issuance (a PKI is a neighbouring system with its own board). - Customer-managed keys for tenant data - the same envelope pattern, a different product. - Detecting secrets committed to Git - a scanner, upstream of this. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps; a schema of 6 tables and 40 columns. ### DNS - the cache that runs the internet - URL: https://tierslate.com/templates/dns-resolver - Kind: system architecture board; level: starter - Access: paid, 200 AI credits - the question is public, the worked answer is not How a name becomes an address through layers of caches, and why the TTL decides how fast a mistake spreads. What is hard about it, left in on purpose: The board is caught mid-migration: shop.example still carries a TTL of 86,400 seconds, so when the A record is moved to the new load balancer, resolvers that cached the old address will keep sending users to 203.0.113.10 for up to a day, and nobody who owns the zone can purge a cache they do not run. If the change is itself the mistake, the rollback is just as slow. The TTL should have been lowered to 300 a full old-TTL ahead of the change - a day of planning nobody remembers to do. Some runtimes and resolvers do not even honour the number, clamping it upward or caching past it. And the zone is served by a single DNS provider: its anycast network absorbs ordinary traffic easily, but if that one provider is degraded or attacked, every resolver whose cache has expired gets no answer at all - exactly the 2016 pattern in which one provider's outage took down large parts of the web at once. #### Start here **Why this design** DNS is the oldest large distributed cache in production and the one every system on every other board quietly depends on. "What happens when you type a URL" interviews start here, and so do a surprising number of real outages: a record changed with a long TTL still in force, a provider down, a resolver timing out. The useful mental model is not the tree of root, TLD and authority - it is that the tree is almost never walked, because caches at every layer answer first, and every one of them is governed by a number chosen by someone else. **What you will learn** - The full resolution path - browser, OS stub, recursive resolver, root, TLD, authority - and why a warm resolver skips most of it. - Why a resolver answers 85% of queries from cache, and what sizes its cache (about 5 GB for an ISP of four million homes). - How the TTL sets both the load on the authoritative servers and the lifetime of a mistake, and why lowering it must happen one old-TTL in advance. - Negative caching: why a typo'd record that briefly did not exist can keep failing after it is fixed. - Why authoritative DNS is a single point of failure for everything behind it, and what two providers buys. **How to read this board** - Press Play: the walkthrough resolves shop.example cold, then warm, then follows a record change and shows where the old answer keeps living. - THE ASKER (top left) holds two caches on one machine; THE ADDRESS (bottom left) is the old and new load balancer. - THE RECURSIVE RESOLVER (middle) is the cache that runs the internet; THE AUTHORITY (right) is where the record is edited. - 1 · Requirements, 2 · Capacity, 3 · Storage - from the point of view of both a domain owner and a resolver operator. - 4 · Availability and 5 · How it works, then 6 · Deployment - the TTL-lowering ritual for a migration. - Finally the DATA MODEL, then argue with the weakness: a one-day TTL on the record being migrated, and one provider. #### 1 · Requirements **Ask these before drawing anything** - Whose side are we designing - the domain owner's authoritative DNS, or a resolver operator's recursive service? They have opposite load profiles and the board shows both. - How quickly must an address change take effect? That number IS the TTL, and its cost is load on the authority and a cold lookup for more users. - Is DNS used for failover? Then the TTL is the failover time, plus whatever the slowest client adds. - How many providers serve the zone? One provider is one failure domain for every service under the name. - Is the zone signed (DNSSEC)? Signing adds key rollovers, larger responses and a new way to make the whole zone fail validation. - Do clients honour TTLs? Browsers keep their own short cache; some runtimes and corporate resolvers clamp or extend TTLs regardless of what was published. **Functional** - Resolve a name and type (A, AAAA, CNAME, MX, TXT) to its records, recursively, on behalf of a client. - Cache positive answers for their TTL and negative answers (NXDOMAIN, NODATA) for the zone's SOA minimum. - Serve authoritative answers for owned zones from every anycast point of presence. - Publish a record change to every authoritative PoP within seconds and bump the zone serial. - Keep an audit of every record change with its previous value and TTL. **Non-functional** - Resolver: cached answer p99 under 2 ms; cold resolution p95 under 150 ms. - Authority: 100% availability is the honest target, because a failed answer fails every service under the name. - Zone change visible on every authoritative PoP: p99 under 30 s. Visible to the world: up to the old TTL, by definition. **Out of scope** - DNSSEC key management (a board of its own). - GeoDNS and latency-based routing - real, and a layer on top of the same caching. - Encrypted transports (DoH, DoT) - they change who sees the queries, not how caching works. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps; a schema of 5 tables and 33 columns. ### Distributed cron - exactly one runner at nine o'clock - URL: https://tierslate.com/templates/cron-scheduler - Kind: system architecture board; level: starter - Access: paid, 200 AI credits - the question is public, the worked answer is not Why a scheduled job needs a lease so it runs once, not once per server, and what a missed tick should do. What is hard about it, left in on purpose: The lease and the unique run row make SCHEDULING exactly-once; nothing makes EXECUTION exactly-once. A worker that charges the customer and then dies before marking the run finished is indistinguishable, to the reaper, from one that died before charging - so the run is retried and the customer is charged twice unless the job itself is idempotent. The fencing token only protects stores that check it: the billing database rejects a stale token, the email provider does not, so a stale worker still sends the invoice email. The board also aligns everybody on the hour: 18,000 hourly jobs plus every five-minute and every-minute job fire in the same second at :00 - about 28,000 runs every hour and 33,000 at midnight UTC, against an average of 52 a second - so the 09:00 job a customer cares about starts at 09:06 behind the queue. And Europe/Berlin has no 02:30 on the last Sunday in March and two of them in October; the misfire policy field does not say which of those a job wants. #### Start here **Why this design** Every company starts with a crontab on one box and eventually loses a month-end report because that box was being patched at midnight. The next step - the same crontab on three boxes - sends every customer three invoices. A distributed scheduler is the answer, and it is a good interview question because the obvious fix (leader election) is necessary, insufficient, and comes with a failure mode that only a fencing token handles. Then the real question arrives: the job ran once, but did it happen once? **What you will learn** - How a lease with a fencing token elects one ticking scheduler, and why the lease alone still allows two leaders for a few seconds. - Why a unique constraint on (job_id, scheduled_for) is the actual exactly-once mechanism for scheduling. - Misfire policies - skip, once, catch up - and why each job needs its own. - Why execution is at-least-once no matter what the scheduler does, and what idempotent jobs look like. - Why the top of the hour is a thundering herd 630 times the average rate, and how jitter flattens it. **How to read this board** - Press Play: the walkthrough follows the 09:00 invoice job from its definition through the lease, the claim and the queue, then breaks it twice. - DEFINITIONS (left) is what should run and the alarm for what did not; THE TICK (middle) is one leader and one row per run; EXECUTION (right) is where exactly-once ends. - 1 · Requirements, 2 · Capacity and 3 · Storage - including the 33,000-run second at midnight. - 4 · Availability - a scheduler outage is silent, which is why the dead-man's switch is on the board. - 5 · How it works in prose, then 6 · Deployment - including how to deploy the scheduler without missing a tick. - Finally the DATA MODEL, then argue with the weakness: scheduling is exactly-once and execution is not. #### 1 · Requirements **Ask these before drawing anything** - What precision is needed - to the second, or within a minute? Second-level precision at scale means a leader scanning every second; minute-level allows a much lazier design. - What should happen to a run missed during an outage? A report wants one late run; a cache warmer wants none; a metering job wants every interval it missed. - May two runs of the same job overlap if one runs long? Forbid, allow, or replace the old one - another per-job field. - Which timezone is "nine o'clock"? A customer's local nine moves twice a year relative to UTC, and on two days a year some local times do not exist or happen twice. - Are the jobs ours or the customers'? A multi-tenant scheduler needs per-tenant limits, or one tenant scheduling 10,000 jobs at midnight starves everyone. - Is the job itself idempotent? If not, no scheduler design delivers exactly-once, and the requirement belongs to the job owner. **Functional** - Register jobs with a cron expression, a timezone, a misfire policy, an overlap policy and a maximum runtime. - Fire each scheduled occurrence once, on time, from exactly one scheduler. - Dispatch each run to a worker, track it by heartbeat, and retry runs whose worker disappeared. - Hand every run a fencing token that downstream writes can check. - Alert when an expected successful run has not happened by its deadline. **Non-functional** - Fire time: within 2 s of the scheduled second at p99, outside the top-of-hour herd. - Leader failover: under 15 s, with missed ticks handled by policy rather than lost. - No duplicate run rows, ever - enforced by the database, not by code. - Scheduler down for an hour must not silently drop an hour of runs. **Out of scope** - Workflow DAGs (job B after job A succeeds) - that is an orchestrator built on top of this. - Sub-second timers and per-user delayed messages - a delay queue, not cron. - The job code itself, beyond the contract that it must be idempotent. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps; a schema of 5 tables and 30 columns. ### Background job queue - at least once, and the retry that doubles it - URL: https://tierslate.com/templates/job-queue - Kind: system architecture board; level: core - Access: paid, 400 AI credits - the question is public, the worked answer is not Why a job queue delivers at least once, and how idempotent workers and dead letters keep retries honest. What is hard about it, left in on purpose: The effect ledger is written in the same transaction that completes the job, so a worker that dies after the provider call and before that commit leaves no trace - the lease expires, another worker runs the job, and the only thing standing between the customer and a second charge is the provider honouring our Idempotency-Key. Payment APIs do. Most email and SMS APIs do not, so a crash in that window sends the welcome email twice, and nothing on this board can prevent it. The 60 second lease has the same hole from the other side: a job that runs long without heartbeating is handed to a second worker while the first is still working. And the queue is a table on the application's own Postgres primary - at 1,850 jobs a second it is about 5,500 row writes a second and 160 million dead tuples a day for vacuum, so a backlog after an outage competes for the same disk as the checkout it was meant to take load off. #### Start here **Why this design** Almost every web application grows a background job queue: send the email after signup, generate the invoice PDF, call the webhook, resize the upload. It is the first place most engineers meet a distributed systems guarantee head on, because a worker can crash between doing the work and saying it did. Interviewers use it to see whether you know what "at least once" means, whether you can make a handler idempotent rather than hoping, and whether you have a plan for the job that fails every single time. **What you will learn** - Why exactly-once delivery is not on offer, and why at-least-once plus an idempotent handler is the honest substitute. - Claiming with FOR UPDATE SKIP LOCKED, leases with a timeout, heartbeats for long jobs, and the reaper that returns abandoned work. - Enqueueing in the same transaction as the business write, and the crash window it closes. - Exponential backoff with jitter, a maximum attempt count, and why dead letters are a product feature. - The one metric that says whether the queue is healthy: the age of the oldest ready job. **How to read this board** - Press Play and follow the walkthrough: ENQUEUE on the left, THE QUEUE, the WORKERS, then SIDE EFFECTS on the right. - Then the failure paths: backoff and the reaper under THE QUEUE, DEAD LETTERS along the bottom. - Then OBSERVE on the left - the single number that pages someone. - 1 · Requirements to 6 · Deployment in the written band, then the DATA MODEL. - Then argue with the tension: the duplicate this design cannot prevent, and the primary it shares. #### 1 · Requirements **Ask these before drawing anything** - What do the jobs DO? A job that writes only to our own database can be made exactly-once with a transaction. A job that calls the outside world cannot, and the answer for each provider is different. - How long does the longest job run? This sets the lease, and a lease shorter than the slowest job is a duplicate generator. - Is order required? FIFO per customer is expensive and almost never needed. Ask for the actual business rule. - What is the acceptable delay? "Within a second" and "within an hour" are different worker pools and different alerts. - What happens to a job that will never succeed? Somebody must look at it. Who, and how quickly? **Functional** - Enqueue a job with a kind, a JSON payload, an optional run_at, a priority and an idempotency key, inside the caller's transaction. - Workers claim ready jobs, run them, and mark them done; at least once delivery. - Failed jobs retry with exponential backoff and jitter up to max_attempts, then move to dead letters. - Abandoned jobs (worker died) are returned to ready when their lease expires. - Dead letters can be inspected and redriven individually or in bulk after a fix. - Scheduled and recurring jobs are enqueued by a cron enqueuer with run_at in the future. **Non-functional** - Pick-up latency: p99 under 2 seconds from ready to running at normal load. - Throughput: 2,000 jobs a second sustained, with headroom to drain a one hour backlog in under 30 minutes. - No job lost after the enqueueing transaction commits. - Duplicates bounded to crashes inside the side-effect window, and every handler documents what a duplicate does. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps; a schema of 5 tables and 41 columns. ### Code execution sandbox - running a stranger's program - URL: https://tierslate.com/templates/code-execution-sandbox - Kind: system architecture board; level: deep - Access: paid, 600 AI credits - the question is public, the worked answer is not How untrusted code is run in isolated, time-limited sandboxes, and why the queue in front of them is the real system. What is hard about it, left in on purpose: A contest opens and submissions jump from 50 a second to 400 within one minute, but a new worker host takes about three minutes to boot and join, so the spike is over before the autoscaler has helped - the queue absorbs it, and the only real fix on this board is pre-warming the fleet from the contest calendar, which is paid for whether anybody shows up or not. The isolation rule (one fresh microVM per run, never reused) is what makes the warm pool necessary, and a warm pool sized for the peak is mostly idle memory the rest of the day. Verdicts near the time limit are not deterministic: CPU time on a shared host varies by 10 to 15 percent with its neighbours, so the same 1.95 s solution is Accepted on one run and Time Limit Exceeded on the next. And a job lease that expires while a slow run is still going means the scheduler hands it to a second host - the program runs twice, and only the idempotent result write keeps the user from seeing two different verdicts. #### Start here **Why this design** This is the system behind every online judge, coding interview pad and "run" button in a tutorial. It is asked because it combines two hard things that usually live in different teams: security isolation against a workload that is actively hostile, and queueing under a demand curve that has a cliff in it every time a contest starts. Candidates usually spend the whole hour on Docker flags. The interesting answer spends half of it on the queue. **What you will learn** - Why a container is not a sandbox for untrusted code, and what a microVM (Firecracker, roughly 125 ms cold boot and under 5 MiB of overhead) buys over gVisor and plain namespaces. - The limits every run gets - CPU time, wall time, memory, processes, output bytes, no network - and which of them actually stop which attack. - Why one VM per run, never reused, and how a warm pool of snapshot-restored VMs pays for that rule. - Why the queue is the system: admission, per-user fairness, leases, and why autoscaling cannot catch a contest spike. - Why verdicts near the time limit are not reproducible, and the three things that make them less wrong. **How to read this board** - Press Play. A submission arrives TOP LEFT, is admitted, and lands in THE QUEUE at the bottom left. - The scheduler leases it, takes a warm VM and places it on a host in SCHEDULE AND ISOLATE. - The watchdog enforces the limits; the judge compares output on the RIGHT and the verdict is pushed back. - Then pages 1 to 6, and the DATA MODEL under them. - Argue with the weaknesses: the contest spike the autoscaler cannot catch, the idle warm pool, and the 1.95-second solution that passes on Tuesday. #### 1 · Requirements **Ask these before drawing anything** - Judge or playground? A judge runs a fixed program against hidden test cases and returns a verdict. A playground runs interactively with stdin and maybe a terminal. They share isolation and differ in everything else; this board is a judge. - Which languages? Each one is a root filesystem image with its own startup cost - C++ is ready in 5 ms, the JVM needs 400 ms before main(), and that startup must not count against the user's time limit. - Is the time limit CPU time or wall time? CPU time is fairer; wall time catches the program that sleeps or blocks. Use both: CPU for the verdict, wall as the kill. - Does user code ever need the network? For a judge, no - and "no network device at all" is far stronger than a firewall rule. - Are there scheduled contests? A known spike can be pre-warmed. An unknown one cannot. **Functional** - Accept a submission (source, language, problem) and return a verdict per test case: Accepted, Wrong Answer, Time Limit, Memory Limit, Runtime Error, Compile Error. - Report CPU time and peak memory per test case. - Submitting twice with the same idempotency key creates one submission. - Push the verdict to the browser; polling is a fallback. **Non-functional** - No submission can read another submission's code, output or test cases, reach the network, or affect the host. This outranks every other requirement. - Queue wait p95 under 2 s on a normal day, under 30 s during a contest peak. - A host dying mid-run loses no submission; it is re-run elsewhere. - Per-user fairness: one user submitting 500 times cannot delay another user's single submission. **Out of scope** - Plagiarism detection. - Interactive debuggers and terminals. - GPU workloads. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps; a schema of 6 tables and 40 columns. ### Distributed log - Kafka from the partition up - URL: https://tierslate.com/templates/message-queue - Kind: system architecture board; level: deep - Access: paid, 800 AI credits - the question is public, the worked answer is not Why an append-only partitioned log gives ordering and replay, and what replication and consumer offsets cost. What is hard about it, left in on purpose: Ordering exists only inside a partition, and a key reaches its partition by hash(key) mod the partition count - so the partition count is fixed for the life of any key that needs ordering. Adding partitions to relieve load silently remaps keys, and for the minutes that old and new records for one order sit in two different logs, two consumers process them in the wrong order. The same hash pins a hot key (one merchant, one tenant) to one partition, and a partition is consumed by exactly one member of a group, so one key caps the whole pipeline at one thread. Durability has its own trade: acks=all with min.insync.replicas=2 on a replication factor of 3 means a single slow broker raises every produce latency, and losing two brokers in one partition STOPS writes to it rather than accepting them on one copy. Turning on unclean leader election restores availability by electing a replica that is missing acknowledged records - data loss the producer was told could not happen. And the consumer commits offsets after processing, so every crash replays a window of records: exactly-once inside Kafka is real (idempotent producer plus transactions), but the moment a consumer writes to a database or calls a payment API, idempotency is the application's job and nothing on this board does it. #### Start here **Why this design** A distributed log is the answer to "how do twenty services find out that an order was placed, in order, without the order service calling each of them". Kafka, Redpanda, Pulsar and Kinesis all answer it the same way: write every event once to an append-only, partitioned, replicated log, and let each reader keep its own bookmark. The interview is rarely about the API. It is about whether you know that ordering is per partition, that an acknowledgement means "in the in-sync replica set" rather than "on a disk", and that a consumer which commits after processing will process some records twice. **What you will learn** - How hash(key) mod 96 picks a partition, and why that modulo makes the partition count a decision you make once. - Why the producer batches for 5 ms into 64 KB batches, and how that turns 200,000 records/s into about 3,100 requests/s. - What acks=all, min.insync.replicas=2 and the high watermark each guarantee - and the exact moment a lagging follower makes that guarantee weaker. - Why followers PULL from the leader, and why the leader epoch exists so a returning ex-leader truncates its tail instead of keeping it. - How a consumer group splits partitions, why it cannot split one, and what a rebalance costs a 96-partition group. - The sizing: 240 MB/s in at peak, 720 MB/s of disk writes, 12 brokers, 104 TB local plus 41 TB tiered over 7 days. **How to read this board** - Press play: eight steps follow one record from the producer's hash to a consumer's committed offset. - PRODUCE: the producer, the partitioner (a parallelogram, because it is a function inside the client) and the batch buffer. - ONE PARTITION: the leader, two followers - one of them in red because it fell out of the ISR - the high watermark diamond and the KRaft controller. - KEEP: segments, the tiered store, and compaction, which is a different retention model rather than a cleanup job. - CONSUME: the group coordinator, two consumers - the red one owns the hot partition - the offsets log and the dead-letter topic. - Pages 1 to 6 in order, then the DATA MODEL: brokers, topics, partitions, replicas, groups and committed offsets. #### 1 · Requirements **Ask these before drawing anything** - What must be ordered relative to what? "All events for one order" is a partition key. "All events, globally" is one partition and about 30 MB/s, which is a very different system. The answer is almost always per entity, and somebody has to say which entity. - Is replay a requirement? If a new service must read the last 7 days, the log is the source of truth and retention is storage you are paying for on purpose. If not, a queue that deletes on acknowledge is cheaper. - What does a duplicate cost? At-least-once is the default. If a duplicate charge is unacceptable, the consumer needs an idempotency key, and that is a table in the consumer's database, not a Kafka setting. - What does a lost acknowledged message cost? This decides acks, min.insync.replicas and whether unclean leader election is ever allowed. - How skewed are the keys? One tenant producing 40 percent of traffic turns a 96-partition topic into one partition and 95 idle ones. - How fast must a consumer see a record? Batching and the high watermark both add latency; 10 ms end to end is achievable, 1 ms is not this design. **Functional** - Produce a keyed record to a topic; records with the same key are delivered in the order produced. - Many independent consumer groups read the same topic, each at its own position. - A consumer may rewind to any offset, or to a timestamp, inside the retention window. - Retention by time (7 days) or by key (compaction keeps the latest value per key forever). - Idempotent produce: a retried batch is never written twice. - Transactions: a consume-transform-produce step either commits its output records and its input offsets together, or neither. **Non-functional** - 200,000 records/s sustained, 600,000/s peak, average record 1 KB. - p99 produce latency of 15 ms at acks=all; p99 end-to-end (produce to consumer poll) of 50 ms. - An acknowledged record survives the loss of any one broker, with no human involved. - Losing one broker of three in a partition has no effect on writes; losing two stops writes to that partition rather than accepting them on one copy. - Adding a consumer group costs disk reads on the brokers, not a change to any producer. **Out of scope** - Per-message routing, priorities or delayed delivery. A log has none of them; a queue built on top of it does. - Exactly-once side effects outside the cluster. Transactions stop at the broker boundary. - Global ordering across partitions. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps; a schema of 6 tables and 38 columns. ### Consensus - a leader, a log, and a majority - URL: https://tierslate.com/templates/raft-consensus - Kind: system architecture board; level: deep - Access: paid, 800 AI credits - the question is public, the worked answer is not How Raft keeps replicas agreeing through elections and a replicated log, and what a network partition really does. What is hard about it, left in on purpose: Every write is ordered by one leader and waits for a majority to fsync it, so throughput is one machine's and latency is the round trip to the third-fastest of five disks - placing the five across three regions for survivability turns a 2 ms write into a 70 ms one. When the leader dies nothing can be written for an election timeout plus a round of voting, typically 300 ms to 2 s, and setting the timeout lower to shrink that makes a GC pause or a congested link trigger needless elections. A partition leaves the minority side completely unable to commit; an old leader stranded there keeps accepting writes it can never commit, so its clients get timeouts whose outcome is genuinely unknown and must be retried with a session id or risk applying twice. Reads are worse than they look: a leader answering from its own state machine can serve a value the majority already overwrote, and the two fixes cost either a heartbeat round per read (ReadIndex) or a bound on clock drift between machines (leader leases) - a timing assumption in an algorithm otherwise chosen because it makes none. Five nodes tolerate two failures and six still tolerate only two, so an even-sized cluster buys cost and nothing else; and changing membership one node at a time is safe only if every step completes, so a half-finished change during an outage is the classic way to lose quorum permanently. #### Start here **Why this design** etcd under Kubernetes, Consul, CockroachDB and TiKV ranges, Kafka's KRaft controller and MongoDB's replica sets all run some form of Raft. It is how a small group of machines agrees on one ordered history despite crashes and partitions, and it sits under every system that needs "exactly one leader" or "this config change happened, everywhere, once". The interview question is usually "design a strongly consistent key-value store" or "how does leader election work", and the depth is in three places: why a majority is the magic number, what a partition does to each side, and why a read is harder than a write. **What you will learn** - Terms, randomised election timeouts, and the one vote rule that guarantees a new leader holds every committed entry. - AppendEntries and its consistency check (prevLogIndex, prevLogTerm), and how a follower's divergent tail is cut off. - Why commit means "on a majority's disk", and why a leader may only count replicas for entries from its own term. - Exactly what each side of a 3-2 partition can and cannot do, including the stranded old leader. - PreVote, and why without it a node that rejoins a cluster can force a healthy leader to step down. - ReadIndex versus leader leases, and what each assumes. - The sizing: 5 voters, 5,000 writes/s, a 2.4 ms write in one region, an 8 GB state machine and snapshots every 13 minutes. **How to read this board** - Press play: eight steps follow one write to commit, then the partition, the election that cannot finish, and a read that must not be stale. - CLIENT: the caller, the session id that makes retries safe, and ReadIndex. - THE LEADER: one writer for term 7, its log, the majority check and the state machine. - MAJORITY SIDE and MINORITY SIDE: the same five nodes, cut 3-2 by a partition. Red is the side that cannot commit. - ELECTION: the timer, the vote rule and PreVote. Then snapshots and membership change. - Pages 1 to 6, then the DATA MODEL: the state each node must persist, as tables. #### 1 · Requirements **Ask these before drawing anything** - What is replicated? Cluster metadata (thousands of writes a second, gigabytes) suits one Raft group. A database (millions of writes, terabytes) needs thousands of groups, one per key range. - How many failures must it survive, and of what? Two simultaneous node failures needs five voters. A whole region needs voters in three regions and pays cross-region latency on every write. - Must reads be linearizable? If a reader may see a value up to a second stale, followers can serve reads and the leader is not the read bottleneck. If not, every read goes through the leader with a confirmation. - What does an ambiguous write cost? A client that times out does not know whether its write committed. The answer decides whether you need client sessions. - How large is the state? It decides snapshot size, and so how long a replacement node takes to catch up. **Functional** - Linearizable put, get, delete and compare-and-swap on keys. - Watch: stream every committed change after a revision. - Leases: keys that expire unless renewed, which is how leader election for OTHER services is built on top. - Add and remove members online. - A client retrying an ambiguous write never applies it twice. **Non-functional** - Survive any 2 of 5 node failures with no loss of committed data. - p99 write under 10 ms in one region; reads under 5 ms linearizable. - 5,000 writes/s and 50,000 reads/s sustained on one group. - Leader failover under 2 s with no human involved. - Safety under every partition, message loss, reordering and duplication. Liveness only when a majority can talk. Raft promises safety always and progress only when a majority is connected. That asymmetry is the whole contract: it would rather stop than be wrong. **Out of scope** - Byzantine faults. Raft trusts every node to follow the protocol; a node lying about its log breaks it. - Cross-group transactions. Two Raft groups agreeing needs two-phase commit on top. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps; a schema of 6 tables and 39 columns. ### Object storage - eleven nines from cheap disks - URL: https://tierslate.com/templates/object-storage - Kind: system architecture board; level: deep - Access: paid, 800 AI credits - the question is public, the worked answer is not How objects are split, erasure-coded and placed across failure domains so losing disks never loses data. What is hard about it, left in on purpose: RS(9,6) stores 1.67 bytes per byte instead of 3 and survives a zone loss plus one more disk, but every saving is paid elsewhere. Rebuilding one lost fragment reads nine others, so a dead 20 TB disk costs about 144 TB of cross-rack network to repair, and repair speed IS durability - halve it and the loss probability rises by orders of magnitude. Every PUT writes fifteen fragments across three zones, so writes pay cross-zone latency and transfer, and with a zone down every read of its objects needs nine fragments from the other two, turning a 20 ms GET into a reconstruction over the zone links. Small objects cannot be split fifteen ways sensibly, so they are packed into 256 MB volumes, which turns DELETE into garbage plus a compaction job that rewrites whole volumes and costs I/O forever. The metadata index is the single point where an object exists or does not - it gives strong read-after-write and list-after-write, and it is also the scaling ceiling: 50 billion rows, hot key prefixes that must be split under load, and an outage there makes every byte unreachable however many disks are healthy. And the eleven nines are a model that assumes independent failures; a buggy release that deletes fragments or a firmware batch that dies together beats any code, and only versioning, object lock and delayed garbage collection defend against that - at the cost of storing deleted data for days. #### Start here **Why this design** S3, Google Cloud Storage, Azure Blob, Ceph RGW and MinIO all sell the same promise - put bytes under a key, get them back, never lose them - and all keep it the same way: separate the small, strongly consistent metadata (which keys exist, where their pieces are) from the enormous, append-mostly data, then cut the data into fragments with an erasure code and spread them so no single failure takes enough. The interview question "design S3" is answered well by someone who can do the durability arithmetic, explain why repair speed matters more than the code, and say what the metadata tier costs. **What you will learn** - Why 3x replication and RS(9,6) survive the same zone-plus-disk failure, at 3x and 1.67x the data. - How placement spreads 15 fragments over 3 zones and 15 racks, and why that rule matters more than the code. - The durability arithmetic: about 11 nines for 3 copies with a 6-hour repair, about 26 for RS(9,6) - and why nobody believes either. - Why the manifest write is the commit point, and how that gives strong read-after-write. - Scrubbing, prioritised repair, and why rebuilding one fragment reads nine. - Why small objects are packed into volumes, and what that does to DELETE. - The sizing: 100 PB logical, 50 billion objects, about 10,400 disks instead of 18,750, and four disk failures a week. **How to read this board** - Press play: eight steps follow one 24 MB upload into fifteen fragments, then a disk failure and its repair, then a small object. - ENTRY: the application, the API front end, and versioning - the defence against bugs rather than disks. - METADATA: the index that decides whether an object exists, its key-range partitions, and lifecycle. - DATA: the erasure coder, the placement decision, the small-object packer, and three zones - the red one has lost a disk. - KEEPING ELEVEN NINES: scrubber, repair queue, rebuilder, garbage collector. - Pages 1 to 6, then the DATA MODEL. #### 1 · Requirements **Ask these before drawing anything** - What durability is promised, and per what? "Eleven nines per object per year" means an expected loss of one object in a hundred billion per year. Write it down; every other number follows from it. - Must the store survive a whole zone? If yes, every write crosses zones and the code must be chosen so one zone holds no more fragments than the code can lose. - What is the size distribution? Median objects in most buckets are under 100 KB while most BYTES are in objects over 10 MB. The small ones decide request costs and the large ones decide disk costs. - Is read-after-write consistency required, and for LIST too? It decides how the metadata tier is built. - What is the read pattern? Write once, read rarely (backups) wants cold encodings; hot media wants caches in front. - Is deletion legally required to be prompt? It conflicts with delayed garbage collection, which is a safety net. **Functional** - PUT, GET (including byte ranges), HEAD, DELETE, and LIST by prefix with pagination. - Multipart upload for objects up to 5 TB in parts of 5 MB to 5 GB. - Versioning, delete markers, and object lock (write once, read many) per bucket. - Lifecycle rules: move to a cold tier after N days, expire after M. - Strong read-after-write and list-after-write consistency. **Non-functional** - 99.999999999 percent annual durability per object; 99.99 percent availability. - Survive the loss of one availability zone with no data loss and continued reads and writes. - GET first byte p99 under 100 ms for hot objects; PUT p99 under 200 ms for 1 MB. - 1,000,000 GET/s and 100,000 PUT/s across the region. - Storage cost: under 1.7 raw bytes per logical byte for objects over 1 MB. **Out of scope** - POSIX semantics - rename, append, partial overwrite. An object is replaced whole. - Cross-region replication (it is an asynchronous copy job on top of this design). - A CDN; caching in front of GET is a separate tier. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps; a schema of 6 tables and 43 columns. ## Payments & ledgers ### Double-entry ledger - the balance is not a column - URL: https://tierslate.com/templates/double-entry-ledger - Kind: system architecture board; level: core - Access: paid, 400 AI credits - the question is public, the worked answer is not Teaches why money is stored as immutable movements and read as a derived sum, and what it costs to never update a total. What is hard about it, left in on purpose: The board spends every paragraph arguing that a balance must never be stored, and then stores one. The snapshot exists because a merchant settlement account takes 200,000 entries a day and no screen is going to SUM 365 million rows - but a snapshot is a cached sum, and a cached sum is a balance column with better manners. It can be written by a projector that read a partially-committed batch; it can be built from a replica that was behind; and when it is wrong, the read path returns a number that is confidently, silently, off. The trial-balance job is the only thing that would notice, it runs hourly, and it proves a GLOBAL invariant - debits equal credits across the whole ledger - which is exactly the check that a per-account snapshot error passes. So the system can be internally consistent and still show one customer the wrong balance for an hour, and the design has no per-account proof that runs often enough to catch it. #### Start here **Why this design** Every payments, wallet, marketplace and banking system has a ledger at its centre, and the first design most engineers reach for - a balance column and an UPDATE - is the one that loses money silently. Interviewers use this question to see whether you treat money as an append-only history with derived balances, whether you can state and enforce an invariant (every transaction sums to zero) rather than hoping code respects it, and whether you know where the performance cost of that choice lands. Real finance teams probe the same thing in design review, because a ledger bug is found by an auditor months later, not by an alert. **What you will learn** - Why accounts has no balance column, and the three failures (lost update, half-applied write, undetectable double-apply) that a stored balance invites. - How the balancing check is enforced inside the write transaction, per currency, so no unbalanced state is ever readable - and why integers in minor units, never floats. - Why one writer per shard and never active-active: the invariant is a single-writer property. - How a balance is read as a snapshot plus a tail keyed by entry id, and why the 200,000-entries-a-day merchant account - not the 1.44-a-day average one - forces that mechanism to exist. - How mistakes are corrected by posting reversals, and what the hourly trial balance can and cannot prove. - The honest weakness: a snapshot is a cached sum, and nothing on this board proves it per account. **How to read this board** - Diagram, left to right: POSTING (a transaction balances or is not written), then THE LEDGER (append only, no balance column), then READING (a balance is a snapshot plus a tail), then PROJECTION (where the cached sum is written). Press play for the eight-step walkthrough. - Below it, CORRECTION AND PROOF: reversals and the trial balance - the band with no UPDATE in it. - Then the DATA MODEL: five tables, and the important column is the one that is missing from accounts. - Page 1 · Requirements - the questions to ask first, and the immutability and latency targets. - Pages 2 and 3 · Capacity and Storage - 12 M transactions a day, 6.2 TB a year, and the tail account that makes snapshots necessary. - Page 4 · Availability - why the ledger is single-writer, and what degrades in latency rather than correctness. - Page 5 · How it works - one posting and one balance read told end to end, then Page 6 · Deployment, where the only rollback for a posted entry is another entry. #### 1 · Requirements A ledger is the system of record for money that has moved. It is not a balance service with history bolted on; it is a history from which balances happen to be derivable. Every requirement below is downstream of that one inversion. **Ask these before designing** - Is this the system of record, or a reporting copy? If a regulator or an auditor will read it, entries are immutable and retention is a decade. If it is a convenience projection of somebody else’s ledger, almost nothing here is needed. - Can an account go negative? A customer wallet must not. A revenue account must. A clearing account is expected to be negative for milliseconds. "No negative balances" is not a ledger rule, it is a per-account-type rule, and getting that wrong blocks legitimate postings at 3 a.m. - One currency or many? Multi-currency does not mean a currency column. It means an account has exactly one currency, a transaction balances per currency, and any cross-currency movement is two transactions joined by an FX position account that somebody owns. - How late can a posting be? If a card scheme can send a transaction dated four days ago, the ledger needs both an effective date and a recorded date, and every report has to say which one it used. - Who is allowed to post? If the answer is "any service", the invariant is enforced in fifteen codebases. This board has exactly one writer for that reason. **Functional** - Post a transaction of two or more entries whose amounts sum to zero, atomically. - Reject any transaction that does not balance, before anything is written. - Read an account balance, and read it as of any past instant. - List an account’s entries in stable order, paginated. - Reverse a transaction by posting its mirror image, linked to the original. - Prove, on demand, that the whole ledger balances. **Non-functional** - Entries are immutable. No UPDATE, no DELETE, no exceptions, enforced by revoked grants rather than by code review. The ledger role has INSERT and SELECT on entries and nothing else. - Durability beats latency, every time. Synchronous commit before acknowledgement. A ledger that loses an acknowledged write is not a ledger. - p99 under 150 ms to post a transaction, under 40 ms to read a balance. - 99.99% on reads, 99.95% on writes. Reads serve screens; writes can be queued and retried by the caller with an idempotency key. - The global invariant - sum of all entries = 0 - is checked hourly and alerts loudly. It is the one assertion that cannot be argued with. **Explicitly out of scope** Chart-of-accounts design, tax treatment, period close and journal approval workflow, FX rate sourcing, the payment rails themselves. The ledger records what happened; deciding what should happen is somebody else’s job. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps; a schema of 5 tables and 27 columns. ### Fraud detection - a decision that must fit in 120 milliseconds - URL: https://tierslate.com/templates/fraud-detection - Kind: system architecture board; level: deep - Access: paid, 800 AI credits - the question is public, the worked answer is not Teaches how a risk decision is split between a synchronous call with a hard deadline and an asynchronous review that is allowed to be right. What is hard about it, left in on purpose: When the deadline is missed the scorer fails OPEN - it allows the payment - and that is the correct choice, because a risk engine able to block revenue will eventually block all of it. It is also the exact behaviour an attacker wants, and the attack is cheap: make the feature store slow and every decision becomes an allow. Nothing on this board distinguishes "the feature store is degraded" from "somebody is degrading the feature store", and the timeout rate is a dashboard rather than a circuit breaker. The second unresolved thing is feature freshness: velocity counters are written from a stream that is seconds to minutes behind at peak, so the six-card-attempts-in-one-minute pattern the rules exist to catch is invisible for exactly as long as the lag lasts - and the lag is worst precisely during the traffic spike an attack produces. Reading the counters synchronously from the write path would fix it and would also put a 40 ms dependency inside a 120 ms budget, so the design accepts staleness and does not measure what it costs. #### Start here **Why this design** Fraud detection looks like a machine-learning question and is really a latency-budget question: the decision has to fit inside somebody else's payment, in about 120 ms including every feature it reads. Interviewers use it to probe whether you can split a system into a fast synchronous path with a defined default and a slow asynchronous path that is allowed to be right, and whether you can put a money value on a false positive. Payment companies such as Stripe, Adyen and PayPal run exactly this split. **What you will learn** - How a 120 ms budget is spent - network, 14 pipelined feature reads in 25 ms, rules, model, slack - and why the slack goes first. - Why the deadline guard fails open to ALLOW, and what that costs when an attacker slows the feature store on purpose. - Why the feature store is its own tier: 2,139 lookups/s on average and 25,667/s at peak, against only 153 decisions/s. - Why REVIEW is the outcome that makes the economics work - and why 0.4% of traffic is 165 full-time analysts. - Why every decision stores the feature vector it saw, not just the score, and why that table is 79% of the bytes. - Why the training signal is a chargeback that arrives 45 to 120 days later, and how that biases the model. **How to read this board** - Press play once: the walkthrough follows one payment from the clock starting to the label arriving sixty days later. - IN THE PAYMENT PATH: everything that shares the 120 ms - scorer, features, rules, model, deadline guard. - BESIDE THE PATH: the streaming features, the review queue and the analyst - the half that has hours. - LEARNING: labels, offline training and how a new model reaches the path. - DATA MODEL: store what the model saw, not just what it said. - Then pages 1 to 6 in order: requirements, capacity, storage, availability, how it works, deployment. #### 1 · Requirements This system answers one question - should this payment proceed - inside somebody else’s latency budget, and then answers it again more carefully when nobody is waiting. **Ask these before designing** - What does a false positive cost, in money? Until that number exists, no threshold can be chosen and every argument about the model is an argument about taste. Here: average basket 68 EUR, and about 40% of falsely declined customers never come back, so a false decline costs roughly 2.4x a basket in lifetime terms. - Who owns the loss? If the merchant eats the chargeback, they will accept a looser threshold than the gateway would. If we eat it, we will not. The threshold is a commercial decision wearing an engineering costume. - Is a "review" outcome usable at all? Holding a payment for a human is only possible if the product can say "we are checking" without shipping. A digital-goods merchant has no such state, and for them the three-way decision collapses to two. - How fresh must features be? "Six attempts in the last minute" is a different system from "spend in the last 30 days". The first needs a synchronous counter; the second is a nightly batch. Most teams build one pipeline and are surprised that it serves neither well. - What is the deadline, and what happens at it? Not "the model is fast". A hard number, and a stated default for when it is exceeded. **Functional** - Score a payment and return ALLOW, REVIEW or DECLINE within the deadline. - Record what the model saw, not just what it said, for every decision. - Queue REVIEW decisions for human analysts with the evidence attached. - Accept chargeback labels weeks later and attach them to the original decision. - Let a rule be added, shadowed and enabled without a deploy. **Non-functional** - p99 of 120 ms end to end, measured at the caller, not at the model server. - Fail open at the deadline. Stated as a requirement rather than discovered as a behaviour. A risk engine that can block revenue will one day block all of it. - Every decision is explainable. A stored feature vector and a rule trace, kept for the length of the dispute window. "The model said so" is not an answer to a regulator or to a merchant. - 99.99% availability, with a defined answer when unavailable - which is ALLOW, and which is why the availability number matters less here than anywhere else on these boards. - No PII in the feature store. Hashed identifiers only. The feature store is replicated to every region and is therefore the easiest thing here to leak. **Explicitly out of scope** Model training infrastructure, the payment rails themselves, chargeback representment workflow, identity verification and KYC. This board is about the shape of the decision, not about the mathematics inside it. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps; a schema of 5 tables and 32 columns. ### Payment gateway - what a timeout means when the card may already be charged - URL: https://tierslate.com/templates/payment-gateway - Kind: system architecture board; level: core - Access: paid, 400 AI credits - the question is public, the worked answer is not Teaches how a payment system stays correct when its authoritative dependency can fail in a way that tells you nothing. What is hard about it, left in on purpose: Every retry inside our own boundary is safe - the UNIQUE on idempotency_key sees to that - and none of it helps with the case the board is actually about. When the acquirer times out, the payment sits in UNKNOWN and the only thing that can resolve it is the acquirer itself: either a webhook it may never send, or the settlement file that lands T+1. So a payment can be unresolved for thirty hours, with the cardholder holding a pending debit and the merchant holding no answer, and the design has no way to shorten that window because the fact it needs is not in any database it owns. Worse, the repair loop is drawn with a single reconciliation worker reading a single file: if the file is late, malformed, or the worker crashes halfway through, nothing on this board notices, and the UNKNOWN pile grows silently until a human reads a dashboard. The status-inquiry call to the PSP would cut the window to minutes, and it is not on this board because it is rate-limited to a few hundred a minute and the peak produces thousands of unknowns - so the fix exists and does not fit. #### Start here **Why this design** Payments is the system-design question where "just retry it" is the wrong answer, because a retry can charge somebody twice. Interviewers use it to probe idempotency, state machines and what you do when an authoritative dependency - the acquirer - times out and tells you nothing at all. Teams that run real gateways are judged on exactly that: never losing a payment, never taking money twice, and never guessing at a result they do not have. **What you will learn** - Why the idempotency key is a UNIQUE index claimed before anything else, not a cache entry, and what it does and does not protect. - Why the payment is written PENDING before the acquirer is called, and why our idempotency key and the acquirer's reference are two different keys. - How to treat a timeout as UNKNOWN - neither success nor failure - and why about 19,800 payments a day land there. - How the repair loop works: out-of-order webhooks, then the T+1 settlement file as the only record that is actually true. - Why a gateway writes more than it reads (0.9 : 1), and what that does to caching and capacity planning for a 12x Black Friday peak. - Why auth-then-capture and refund-a-capture-not-an-authorisation show up as foreign keys in the data model. **How to read this board** - Press Play and follow the walkthrough along the top: INTAKE (claim the key), AUTHORISE (the part we own), NOT OURS (the acquirer), then CAPTURE AND SETTLE. - Then WHEN WE DO NOT KNOW underneath - the repair loop that runs on the acquirer's clock. - 1 · Requirements - the questions that decide whether half this board exists. - 2 · Capacity estimation and 3 · Storage estimation - the five writes behind one payment. - 4 · Availability - what may be slow, and what must never be wrong. - 5 · How it works, then 6 · Deployment plan. - Finally the DATA MODEL below the band - five unique constraints, five failures prevented - and then argue with the weakness left in on purpose: a payment can sit in UNKNOWN for thirty hours, and nothing notices if the settlement file is late. #### 1 · Requirements A gateway sits between a merchant who wants a yes/no and an acquirer who will sometimes give neither. It owns exactly one thing: an honest, durable record of every attempt to move money, and the discipline never to guess at one. **Ask these before designing** - Are we the merchant of record, or a technical gateway? If we hold the funds we owe a ledger, settlement, payouts and a regulator. If we only route, we owe an audit trail and nothing else. The answer changes whether half this board exists. - Auth-then-capture, or sale? A separate capture is what lets a shipped-in-three-days business take the money when it ships. It also creates a second network call that can time out, and a 7-day expiry on the authorisation. A single "sale" call halves the failure surface and removes the option. - How many acquirers? One acquirer is one outage away from zero revenue. Two acquirers means routing logic, two idempotency dialects, two reconciliation formats, and two settlement calendars. - Does the merchant expect a synchronous answer? If yes, the UNKNOWN case has to be resolved into a lie or a 202. If the merchant will accept a webhook, UNKNOWN can be told honestly and the whole design gets simpler. - What is the refund window? 120 days of refundability means captures stay addressable for 120 days, which is a retention requirement disguised as a product decision. **Functional** - Authorise a card for an amount, returning a stable payment id. - Capture all or part of an authorisation, once. - Refund all or part of a capture, any number of times up to the captured total. - A retried request with the same Idempotency-Key returns the first answer and performs no second money movement. - Every state change is an appended event with the acquirer reference that caused it. - Reconcile against the acquirer daily and report every disagreement. **Non-functional** - Never double-charge, even at the cost of declining. A duplicate charge costs the chargeback, the refund, the fee, the support contact and the merchant relationship. A false decline costs one retry. - p99 under 3.5 s for POST /payments, of which the acquirer is 2.1 s and is not ours. Our own budget is 400 ms. - 99.99% on the authorisation path (52 min/year) measured as "we returned a definite answer or an honest 202", not as "we returned 200". - Zero tolerance for lost writes. Synchronous replication before we acknowledge, always. A payment we acknowledged and then lost is indistinguishable from fraud in an audit. - Every amount is an integer of minor units with an ISO 4217 currency. Floats are not a rounding inconvenience here, they are a reconciliation break. **Explicitly out of scope** Fraud scoring (its own board), 3-D Secure step-up flows, tokenised wallet decryption, merchant onboarding and KYC, payout scheduling. Each is real; none changes the shape of the timeout problem. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 9 steps; a schema of 5 tables and 35 columns. ### Matching engine - price-time priority, and why the clock cannot decide who was first - URL: https://tierslate.com/templates/stock-exchange - Kind: system architecture board; level: deep - Access: paid, 800 AI credits - the question is public, the worked answer is not Teaches why an exchange assigns order with a sequence number instead of measuring it with a clock, and what a single-threaded book buys in return. What is hard about it, left in on purpose: Every good property on this board - deterministic matching, a standby that recovers by replay, a surveillance answer that is a fact rather than an opinion - is bought by funnelling the entire market through one sequencer and one matching thread. That is one machine’s worth of throughput and one machine’s worth of failure. The failover is drawn as a hot standby replaying the journal, which is honest and incomplete: promotion takes 200-400 ms, during which the market is halted, and the new primary must prove it has consumed every sequence number the old one published or it will re-use numbers that are already on the wire and in a thousand subscribers’ books. Nothing on this board arbitrates that race - there is no external quorum, only a heartbeat, so a network partition that leaves both engines believing they are primary produces two divergent books and two conflicting trade streams, and the only recovery is a busted-trade process run by humans the next morning. The sharding escape hatch (one engine per instrument group) removes the throughput ceiling and does not remove the problem: it multiplies the number of single points of failure, and any order type spanning two instruments loses determinism entirely. #### Start here **Why this design** A matching engine is the cleanest test there is of whether you know when NOT to distribute something. Interviewers use it to see whether you reach for timestamps, shards and locks by reflex, or notice that fairness needs one agreed order of events and that the live state of a whole national market fits in 768 MB on one machine. Real venues are built exactly this way, and the regulator checks the result: every trade must be reproducible from the journal years later. **What you will learn** - Why order is ASSIGNED by a sequencer as a monotonic integer rather than measured with a clock: two gateways’ crystals disagree by more than the 300 ns between two orders. - Price-time priority as a data structure: two price-ordered sides, a FIFO queue per price level, and the tie-break done by sequence number. - Why one thread on a pinned core beats a locked concurrent book, and how it matches in microseconds at 52,000 messages/s at the open. - Journal-before-engine, and how determinism turns into the replication strategy: the hot standby recovers by replaying, not by copying state. - Why market data goes out over A/B multicast with gap replay, at 37 Mbit/s rather than the 9.4 Gbit/s unicast would cost. - Why 72% of messages are cancels, and why that ratio rather than the trade rate sizes the engine. **How to read this board** - Diagram, left to right: IN (gateway, risk check, sequencer, journal), then THE ENGINE (one thread, the book, the standby), then OUT (execution reports and the multicast feed). Press play for the eight-step walkthrough. - Then the band underneath: AFTER THE TRADE - drop copy, trade store, clearing and surveillance, all reading the same numbered stream. - Page 1 · Requirements - the questions to ask first, and why determinism outranks latency. - Pages 2 and 3 · Capacity and Storage - the numbers behind 52,000 msg/s at the open, the 768 MB book and 41 TB of seven-year history. - Page 4 · Availability - the sequencer, engine and multicast feed named as single points of failure, and what degrades. - Page 5 · How it works - the whole path told end to end - then Page 6 · Deployment plan, including the replay gate that every engine change must pass. - The DATA MODEL region between the diagram and these pages: five tables, and the column that matters is orders.sequence. - Finish on the deliberate weakness: the whole market funnels through one sequencer and one thread. Failover halts the market for 200-400 ms and nothing arbitrates a split brain - that is the conversation to have. #### 1 · Requirements An exchange is a fairness machine. Its product is not throughput and not latency - it is the credible claim that the same rules were applied to everybody in the same order, and that the order can be proved afterwards. **Ask these before designing** - Continuous trading, or auctions? Continuous matching is the interesting engine. Opening and closing auctions are a different algorithm - collect, then cross at one price - and most real venues need both, which means two matching modes over one book. - Which order types? Limit and market is a weekend project. Add stop, iceberg, pegged, and self-trade prevention and the state machine triples. Each new type must preserve determinism, which is why order types are added at roughly one per year and not one per sprint. - Is there a maker-taker fee? Fees change behaviour, and behaviour changes load. A rebate for resting orders produces a market where 70-80% of messages are cancels, which is a capacity requirement disguised as a pricing decision. - Who may be first? Strict price-time is one answer. Pro-rata allocation is another, and size-priority is a third. This is a market-structure decision that determines the core data structure, so it cannot be deferred. - What happens on a halt? Not an edge case: circuit breakers fire on ordinary days. Does the book persist across the halt, or is it purged? Both are defensible, and only one can be true. **Functional** - Accept, amend and cancel orders; every message acknowledged with its sequence number. - Match strictly by price, then by sequence, and never by timestamp. - Publish every book change to market data, in sequence, to everybody at once. - Publish executions privately to the two participants involved. - Replay any session from the journal and reproduce the same trades, exactly. - Halt and resume an instrument without losing the book. **Non-functional** - Determinism above everything. Same input sequence, same output, every time, on any machine. Without this there is no standby, no replay, no surveillance and no defence in a dispute. - p99 tick-to-trade under 20 microseconds inside the engine; p99 gateway-to-ack under 250 microseconds. - Fairness is measured, not asserted. The spread between the fastest and slowest gateway is published; if one path is reliably quicker, participants pay for it and the venue has an integrity problem. - No message loss, ever. The journal is written before the engine sees the message, and an unjournalled message does not exist. - Seven years of replayable history, because that is what a regulator asks for and the unit of the request is "the whole session". **Explicitly out of scope** Clearing and settlement (drawn, not designed), market surveillance algorithms, colocation and the physical network, listing and corporate actions, fee schedules. This board is about the moment two orders meet. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps; a schema of 5 tables and 30 columns. ### Wallet transfers - the state machine, the hold that expires, and the reversal that cannot - URL: https://tierslate.com/templates/wallet-transfers - Kind: system architecture board; level: starter - Access: paid, 200 AI credits - the question is public, the worked answer is not Teaches how money moves between two accounts in one system without ever being in both places or neither, and what it costs to make every step undoable. What is hard about it, left in on purpose: Everything on the happy path is transactional and safe, and the entire risk of the system lives in the one state the transaction cannot cover: PENDING_EXTERNAL, where a hold exists on the sender’s money and the bank rail has been asked to move it. The hold sweeper is the only thing that ever rescues that state, it runs on a clock, and it cannot tell a dead transfer from a slow one - so it will eventually release a hold for a payout the bank is still processing, at which point the sender can spend money that is about to leave, and the wallet goes negative in a way no CHECK constraint prevented because the money was legitimately spent twice by two legitimate paths. Lengthening the expiry makes customers’ money unusable for longer; shortening it makes the race more likely. The second unresolved thing is the reversal, which is drawn as an ordinary transfer in the opposite direction and is therefore subject to every failure the original was - including insufficient funds. A recipient who has already spent the money cannot be reversed, and nothing on this board decides what happens then: the balance would go negative, the CHECK refuses it, and the reversal simply fails with no defined next step other than a human. #### Start here **Why this design** Moving money between two wallets in one database looks like a single transaction, and interviewers ask it to find out whether you notice where that stops being true. A payout to a bank, a limits check, a notification and a reversal weeks later each add a state the transaction cannot cover. Every fintech and marketplace wallet has this system, and the bugs it produces are regulatory events with a customer’s name on them, not stack traces. **What you will learn** - How one idempotency key with a UNIQUE index stops a retried request from sending the money twice. - How a hold makes money unspendable before it moves (spendable = balance_minor - held_minor), and the two CHECK constraints that make the database, not the app, the safety argument. - Why the 97% internal path is one plain transaction with no saga, and why adding one would be a downgrade. - Why the 3% with an external leg sit in PENDING_EXTERNAL for up to three days, and hold 99.9% of the 54 MB of contended state. - Why a reversal is a second transfer and never an UPDATE, and how it can itself fail on insufficient funds. - The nightly identity check: wallet balances plus live holds must equal the bank float. **How to read this board** - Diagram, left to right: THE REQUEST (one key, one transfer), then THE STATE MACHINE (limits, hold, move, spend), then OUTSIDE (the bank rail), then AFTER (events, notifications and the nightly reconciliation). Press play for the eight-step walkthrough. - Then the band underneath: WHAT UNDOES A TRANSFER - the hold sweeper and the reversal, drawn under the path because compensation runs backwards. - Page 1 · Requirements - the questions that decide whether this is one transaction or a state machine. - Pages 2 and 3 · Capacity and Storage - 10M transfers/day, seven writes each, 60 TB over five years, and a holds table that must never grow. - Page 4 · Availability - the single points of failure, including a sweeper whose failure has no error message. - Page 5 · How it works - one transfer told end to end - then Page 6 · Deployment plan, where wallets are never active-active. - The DATA MODEL region between the diagram and these pages: five tables, and the two CHECK constraints that carry the safety argument. - Finish on the deliberate weakness: the sweeper is the only rescue for PENDING_EXTERNAL and cannot tell a dead payout from a slow one, and a reversal can fail with no defined next step. That is the conversation to have. #### 1 · Requirements A wallet transfer looks like the simplest money problem in this collection - two rows, one database, subtract here and add there. The requirements below are all the ways that sentence turns out to be incomplete. **Ask these before designing** - Can a transfer leave the system? If every transfer is wallet-to-wallet, the whole thing is one database transaction and most of this board is unnecessary. The instant a payout to a bank exists, there is a state that can be pending for three days, and that single state is where all the risk lives. - Can a wallet go negative? The honest answer for most products is "never, except for fees and reversals", and those two exceptions are what make it a CHECK with a carve-out rather than a CHECK. - Who may reverse, and for how long? Support reversing a transfer from last Tuesday is a different system from a 30-second undo window. The first needs a reversal that can fail; the second can just cancel. - Is the recipient notified before the money is final? If yes, a later reversal is a conversation with an angry customer. If no, the product feels slow. Most teams pick "yes" without noticing they have chosen the conversation. - One currency per wallet? Multi-currency wallets mean an FX rate at transfer time, a rate that can move between quote and execution, and somebody owning the resulting position. That is a second system, not a column. **Functional** - Transfer an amount from one wallet to another, atomically, with an idempotency key. - Hold funds before an external leg, and release the hold if it does not complete. - Reverse a completed transfer by posting the opposite movement, linked to the original. - Show a balance that never includes money that is held. - List a wallet’s transfers with a stable order and a stable page boundary. - Prove daily that wallet balances plus live holds equal the bank float. **Non-functional** - A wallet may never go negative, enforced by a database CHECK rather than by application logic, because the application will eventually have two versions running at once. - No money is ever in both wallets or neither. The internal leg is one transaction; there is no saga for it, and introducing one would be a downgrade. - p99 under 400 ms for an internal transfer, which is entirely dominated by the limits check. - 99.95% on the transfer path, 99.99% on balance reads. People check their balance far more often than they send money, and a balance page that is down feels like the money is gone. - Read-your-writes on balances, always. A sender who sees their old balance after sending assumes it failed and sends again. **Explicitly out of scope** The underlying ledger (its own board), card issuing, KYC and onboarding, FX, and the bank rail’s internals. This board is about the state machine around a movement, not about the movement itself. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps; a schema of 5 tables and 32 columns. ### OTP verification - six digits and a clock - URL: https://tierslate.com/templates/otp-verification - Kind: system architecture board; level: starter - Access: paid, 200 AI credits - the question is public, the worked answer is not How a one-time code is issued, rate limited and checked, and why the SMS bill is the attack surface. What is hard about it, left in on purpose: Every limit on this board is keyed by something the attacker chooses. Per-number limits never fire against SMS pumping because each send goes to a fresh premium number, and per-IP limits fall to a residential proxy pool, so the only control that sees the attack is the per-country spend breaker - and when it trips it blocks every legitimate user in that country too, which is an outage we cause on purpose. The attempt counter lives on the CODE, not the phone number, so asking for a new code resets it: three codes per fifteen minutes is fifteen guesses per window, roughly 1.4 in 1,000 per number per day, which is fine for a login and not fine for a bank transfer. And the code store is Redis with asynchronous replication, so a failover silently loses live codes and every user mid-login has to ask for another one - which is another paid send. #### Start here **Why this design** A one-time passcode looks like the smallest feature in an auth system: generate six digits, text them, compare them. Interviewers like it because the obvious answer is correct about guessing and completely wrong about money. The endpoint that sends codes must be callable by somebody who is not logged in, every call costs a carrier fee, and there is an entire fraud industry - SMS pumping, also called toll fraud or IRSF - built on making other companies pay for texts to numbers the fraudster profits from. **What you will learn** - Why a code is stored as a hash with a TTL and an attempt counter, and the arithmetic that makes six digits and five attempts safe enough for a login. - Why the attempt counter has to be thought about per phone number as well as per code, because re-issuing a code resets it. - Why per-number and per-IP rate limits do not stop SMS pumping, and why a per-country spend ledger does. - Why the sender is behind a queue, and what a 3 to 20 second carrier delay does to "Resend code" buttons. - Why the code store is allowed to be lossy, and what that costs in extra sends. **How to read this board** - Press Play and follow the walkthrough: REQUEST along the top left, ISSUE in the middle, DELIVERY on the right. - Then CHECK on the left, which is where the code comes back - through a person, not a wire. - Then COST at the bottom right, and the dashed wire that runs back from the breaker into the prefix rules. - 1 · Requirements and 2 · Capacity - the numbers, including the one on the invoice. - 3 · Storage, 4 · Availability, 5 · How it works, 6 · Deployment. - Finally the DATA MODEL, then argue with the tension: every limit here is keyed by something the attacker picks. #### 1 · Requirements **Ask these before drawing anything** - What is the code FOR - sign-up, login, or approving a payment? A login code can tolerate a 1 in 200,000 guess rate. A payment approval should bind the code to the amount and the payee, or it can be phished and replayed against a different transfer. - Which channels - SMS only, or also voice, email and an authenticator app? SMS is the expensive, interceptable one, and the honest long-term answer is to move users off it. - Which countries do we actually have users in? This decides the prefix allowlist, and it is the single most effective anti-fraud control on the board. - Is there a budget? Somebody owns the SMS bill. If nobody has said a number, the first pumping attack will say it for them. - What happens when the user does not get the code? "Resend" is a paid action and the most-clicked button on the screen. **Functional** - POST /otp/send issues a six digit code for a phone number and a purpose (login, signup, phone_change, payment). - POST /otp/verify accepts the code and returns a short-lived verification token on a match. - A code expires after five minutes and is burned after five wrong attempts or one right one. - Issuing a new code invalidates the previous one for the same number and purpose. - Sends are limited per number, per IP, per device fingerprint and per country prefix. **Non-functional** - Verify p99 under 50 ms. It is a hash compare; anything slower is a bug. - Send accepted (queued) p99 under 150 ms. Delivery to the handset is 3 to 20 seconds and not ours to promise. - Comparison is constant time, and the response for "wrong code" and "no such code" is identical, so the endpoint cannot be used to discover which numbers have a pending login. - SMS spend never exceeds 3x the trailing seven day baseline for any country for more than ten minutes. - Codes are never logged in plaintext - not in application logs, not in the provider webhook payloads we store. **Out of scope** - Passwords and passkeys. This is the second factor or the passwordless step, not the account system. - SIM swap detection. Worth a carrier API lookup for high-value flows; a different system. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps; a schema of 5 tables and 39 columns. ### Subscription billing - the invoice is computed, not typed - URL: https://tierslate.com/templates/subscription-billing - Kind: system architecture board; level: core - Access: paid, 400 AI credits - the question is public, the worked answer is not How a recurring charge is driven by a billing clock, proration and dunning, and why plan changes are the hard part. What is hard about it, left in on purpose: A PLAN CHANGE AND A RENEWAL CAN LAND ON THE SAME SUBSCRIPTION IN THE SAME SECOND, AND THE BOARD ONLY STOPS THEM BOTH WINNING. The subscription row carries a version and both writers compare-and-swap on it, so one of them is rejected - but the loser simply retries. If the loser was the upgrade, its retry re-reads a subscription whose period has just rolled over and prorates against the NEW period: the customer is credited for unused days of a period that has already been invoiced in full, and charged a proration for days the renewal invoice already covered at the old price. Nothing double-charges, the invoice totals are internally consistent, and the customer is wrong by a few dollars that nobody notices until support does. The second weakness is the anchor: 35% of monthly subscriptions renew at 00:00 UTC on the 1st, so 1,050,000 invoices are due in the same minute against a payment provider that accepts 100 charges a second - collection for the 1st takes three hours, and a provider incident in that window lands entirely on one day of revenue. The clock is safe to rerun; it is not spread out, and the board does not spread it. #### Start here **Why this design** Every SaaS product, streaming service, gym and newspaper bills on a schedule, and the naive design - a cron job per customer that charges a stored amount - works until the first customer upgrades on the 17th. Interviewers ask this question to see whether you model the invoice as something computed from versioned prices and a period, whether you can do proration without floating point or double-counting a day, and whether you separate the invoice (a debt) from the charge (an attempt to settle it). Teams that run billing in production care most about the last part: failed payments, not plan design, decide how much revenue quietly walks away. **What you will learn** - Why a price is a versioned row that is never edited, and why an invoice references the version, not the plan name. - Why the billing clock is a sweep over current_period_end <= now() with an idempotency key, rather than a scheduled job per subscription. - How proration splits a period at the instant of a change, and the worked arithmetic of a $30 to $90 upgrade on day 10. - Why an invoice is immutable once finalised, and corrections are credit notes. - How dunning retries on a schedule, why a provider error must never enter dunning, and why the recovery rate is the most valuable number on the board. - The honest weakness: a plan change racing a renewal at the boundary, and 1,050,000 renewals due in the same minute on the 1st. **How to read this board** - Diagram, left to right: CHANGE (a customer edits the future), THE CLOCK (renewal is a query), COLLECTION (an invoice is a debt, a charge is an attempt), DUNNING (failure is the normal case). Press play for the walkthrough. - Below it, ACCESS: the only thing the customer actually feels. - Then the DATA MODEL: six tables. Look at what is unique on invoices. - Page 1 · Requirements, then Pages 2 and 3 · Capacity and Storage - 3.1 M renewals a month and why storage is not the problem. - Page 4 · Availability - what happens if the clock stops for six hours. - Page 5 · How it works - one upgrade and one renewal end to end, then Page 6 · Deployment, where a billing change is shadowed for a whole cycle. #### 1 · Requirements A billing system turns a promise ("$30 a month for Pro") into a sequence of invoices and a sequence of attempts to collect them. It is not a payments system - it calls one - and it is not a pricing tool. Its job is to compute the right amount, for the right period, exactly once. **Ask these before designing** - Anchor on signup date, or on the 1st? Signup anchoring spreads renewals across the month. "Bill everyone on the 1st" is what finance asks for and it puts a third of the month into one minute. - Proration on upgrade, on downgrade, or both? The common answer is: upgrade immediately and prorate, downgrade at period end with no credit. That asymmetry is a product decision that halves the proration code. - Charge proration now or on the next invoice? Charging now means an extra card attempt (and an extra decline). Deferring means the next invoice carries pending items. - Who owns tax? Sales tax and VAT depend on the customer’s location at the time of the invoice, not at signup. If tax is external, the invoice engine must call it inside finalisation. - What does "past due" switch off? Read-only access, full suspension, or nothing for 14 days? This is the lever that decides whether dunning recovers 40% or 70%. **Functional** - Create, upgrade, downgrade, pause and cancel subscriptions; cancel at period end by default. - At each period end, generate exactly one invoice per subscription per period. - Prorate mid-period changes to the second, in integer minor units. - Collect each invoice; on failure retry on a schedule and notify; suspend after the final retry. - Issue credit notes; never edit a finalised invoice. - Recompute any historical invoice from its inputs and get the same total. **Non-functional** - Exactly one invoice per (subscription, period), enforced by a unique key, not by the scheduler behaving. - Renewal lateness under 5 minutes on a normal day; under 3 hours for collection on the 1st. - Plan change p99 under 400 ms, including the proration preview. - Never charge a card twice for one invoice, across retries, timeouts and provider redeliveries. - Ten-year retention of invoices and lines - they are tax records. **Explicitly out of scope** Usage metering (it feeds this as line items), revenue recognition (it reads finalised invoices), the card network, and pricing experiments. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps; a schema of 6 tables and 48 columns. ### Currency exchange - a rate that is only true for a few seconds - URL: https://tierslate.com/templates/currency-exchange - Kind: system architecture board; level: core - Access: paid, 400 AI credits - the question is public, the worked answer is not Why a quoted FX rate is a short-lived promise with an expiry, and how rounding is a ledger decision. What is hard about it, left in on purpose: EVERY QUOTE IS A FREE 30-SECOND OPTION, AND THE BOARD ONLY PRICES IT FOR A CALM MARKET. The customer sees a frozen rate and may execute at any point before expiry - so a customer who watches the market can request a quote, wait, and accept only if the mid has moved against the house. The 35 bp spread covers that comfortably on a normal day: EUR/USD moves about 1 bp in 30 seconds, one thirty-fifth of the margin. It does not cover a gap. When a central bank surprises the market a pair can move 2% in under a second (EUR/CHF moved 30% in minutes in January 2015), and every live quote for that pair becomes a guaranteed loss the instant it is accepted. The aggregator rejects STALE rates but nothing on this board watches VOLATILITY: there is no breaker that widens spreads or withdraws outstanding quotes when the mid moves more than the spread within a quote lifetime, and the hedge only runs once the net position crosses $250,000 - so the loss is booked before treasury has even been told it exists. #### Start here **Why this design** Any product that lets money cross a border - a wallet, a remittance app, a marketplace paying sellers abroad, a card spending in a foreign currency - has to answer "how much will I get?" with a number it will honour. The naive design reads a rate at the moment of execution and multiplies; the customer sees one number and receives another. Interviewers use this question to see whether you model the quote as a stored, expiring promise, whether you know a cross-currency movement must balance per currency, and whether you can say exactly where a rounding fraction goes. Treasury teams care about the next layer: every executed quote leaves the company holding currency risk until it is hedged. **What you will learn** - Why a quote is a row with a frozen rate, both amounts already rounded, and an expires_at - and why execution spends a quote id, never "the current rate". - How a mid rate is built from three feeds, and why staleness is a reject, not a warning. - How the spread and the rounding rule are applied in that order, in integers, per currency’s minor units (USD 2, JPY 0, KWD 3). - Why a conversion is four ledger entries in two currencies through FX position accounts, and each currency sums to zero. - How the company’s resulting exposure flows to treasury and is netted before it is hedged. - The honest weakness: a 30-second quote is a free option, and nothing here withdraws quotes when the market gaps. **How to read this board** - Diagram, left to right: RATES (a number that decays), QUOTE (a promise with an expiry), EXECUTE (only a live quote can be spent), TREASURY (the risk you kept). Press play for the walkthrough. - Below it, RECONCILIATION: quoted vs executed vs hedged, and where rounding went. - Then the DATA MODEL: five tables. Note conversions is UNIQUE on quote_id. - Pages 1-3 · Requirements, Capacity and Storage: 5 M quotes a day, 1,170 ticks a second in. - Page 4 · Availability - why a stale rate stops quoting rather than guessing. - Page 5 · How it works - 1,000 USD to EUR end to end, then Page 6 · Deployment. #### 1 · Requirements The service quotes and executes currency conversions for customers holding balances in several currencies. It does not set the market price - it buys rates from liquidity providers - but it sets its own spread, owns the rounding rule, and holds the resulting position until treasury covers it. **Ask these before designing** - Firm quotes or indicative? A firm quote is honoured if accepted before expiry. An indicative one is re-priced at execution. Firm is what customers expect and what this board does; it is also what creates the free option. - How long is a quote alive? 30 s is common for retail. Longer helps conversion rate and costs margin; shorter annoys people on slow phones. - Which side is the customer fixing? "Send exactly 1,000 USD" and "receive exactly 900 EUR" are different calculations and round on different sides. - Which way does rounding go, and where does the remainder land? Half-even on the received amount, with the difference absorbed by the FX position account, is one defensible answer. "Always in our favour" is another, and is a regulatory conversation. - Do we hold the risk or pass it through? Matching every conversion one-for-one with a provider trade removes risk and adds 200 ms and a fee to each. Netting and hedging above a threshold is cheaper and leaves the company exposed in between. **Functional** - Build a mid rate per pair from multiple providers; reject a pair whose freshest good rate is older than 2 s. - Issue a firm quote: sell amount, buy amount, all-in rate, expiry. - Execute a conversion against a quote id, once, before expiry, by the customer who requested it. - Post the conversion to the ledger as balanced entries per currency. - Track net position per currency and hedge it above a threshold. - Reconcile daily: quoted margin vs realised margin vs hedge cost. **Non-functional** - Quote p99 under 80 ms; execution p99 under 200 ms. - A rate older than 2 s is never quoted. Better to refuse than to quote a number the market has left. - Every executed conversion is reproducible from the stored quote - rate, rounding, amounts - years later. - Integer minor units in the ledger, always; rates stored as NUMERIC(18,8), never floats. **Out of scope** Payment rails, KYC, card-scheme FX, the provider contracts and treasury’s hedging strategy itself. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps; a schema of 5 tables and 30 columns. ### Loyalty points - a currency you print yourself - URL: https://tierslate.com/templates/loyalty-points - Kind: system architecture board; level: core - Access: paid, 400 AI credits - the question is public, the worked answer is not Why points are a ledger with expiry and liability, and why a balance column is the wrong answer again. What is hard about it, left in on purpose: POINTS VEST AFTER THE 30-DAY RETURN WINDOW, WHICH HANDLES RETURNS - AND A CHARGEBACK CAN ARRIVE 120 DAYS LATER. By then the 168 points from that purchase have vested and may already be inside a 25,000-point flight that has flown. The board claws back by posting a negative entry, which can take a member’s balance below zero; it then blocks redemption until future earning pays the debt off. That is all it does. The value of the reward already fulfilled is unrecoverable, a member who never buys again carries a negative balance forever, and a fraud ring that buys, vests, redeems and charges back is only stopped by the fraud team noticing - nothing here limits redemptions against points younger than the chargeback window. The second weakness is in the number finance books: liability is outstanding points x $0.01 x (1 - 22% breakage), and that 22% is estimated from history. The moment marketing shortens expiry, adds a cheap reward or runs a "your points are expiring" email, breakage falls and the balance sheet was understated - by roughly $5.9 M per point of breakage on 59 billion outstanding points. #### Start here **Why this design** Airlines, coffee chains, supermarkets, card issuers and marketplaces all run points programs, and almost every first version has a points integer on the user and an UPDATE on each purchase. It works until somebody asks which points expire next month, a refunded order needs its points back, or the auditor asks what the program owes. Interviewers use this question to see whether you recognise a ledger problem in disguise, whether you can model expiry without a job that mutates balances, and whether you know that unredeemed points are a liability with real accounting rules (IFRS 15 / ASC 606 treat them as deferred revenue, released on redemption or expected breakage). **What you will learn** - Why points are stored as immutable lots with vests_at and expires_at, and the balance is derived. - How a redemption consumes lots oldest-expiry-first and records exactly which ones, so a cancellation restores the same lots. - Why expiry is a filter at read time and the nightly sweeper only books it. - Why redemption is reserve-then-confirm, because the reward is fulfilled by somebody else. - How liability is computed - outstanding points x value x (1 - breakage) - and why breakage is an estimate that moves. - The honest weakness: chargebacks after vesting, and a negative balance as the only remedy. **How to read this board** - Diagram, left to right: EARN (points are printed on a purchase), THE LEDGER (lots, not a balance), REDEEM (reserve, then confirm), TIME (points die on schedule). Press play for the walkthrough. - Below it, LIABILITY AND CLAWBACK: the promise on the balance sheet, and the case it does not handle. - Then the DATA MODEL: six tables. There is no balance column anywhere. - Pages 1-3 · Requirements, Capacity and Storage: 142 M points printed a day, 59 billion outstanding. - Page 4 · Availability - why earning can be late and redemption cannot double-spend. - Page 5 · How it works, then Page 6 · Deployment, where a rules change is a financial event. #### 1 · Requirements A loyalty program issues a private currency as a reward for spending, and lets members exchange it for goods, discounts or partner rewards. The system is a ledger of that currency plus the rules that create it and the clock that destroys it. **Ask these before designing** - When are points spendable? Immediately is what members want. After the return window (30 days) is what finance wants, because returning a $400 purchase after spending its points is a straight loss. - Does expiry run per lot or per account? "24 months after earning" is per lot. "24 months after your last activity" is per account and far simpler - but then every lot shares one fate and the question "what expires this month" has a different meaning. - Can a balance go negative? It must be allowed to, briefly, for clawback - or clawback must fail, which means the business eats every late refund. - Who fulfils rewards? If an airline issues the ticket, redemption is a two-phase operation with an external party that can say no. - Do partners buy points? A card issuer buying points at $0.007 each makes points cash-backed revenue. The ledger must know which lots were paid for, because their liability is accounted differently. **Functional** - Earn points from purchases and partner events, by rules (base rate, tier multiplier, campaigns), idempotently per order. - Show a balance, a pending balance, and points expiring in the next 30 days. - Redeem for a reward: reserve, then confirm or release. - Expire lots at expires_at; claw back points for refunded or charged-back orders. - Report outstanding liability daily. **Non-functional** - No point is spent twice. Concurrent redemptions by one member are serialised. - Balance read p99 under 50 ms; redemption reserve p99 under 300 ms. - Earning may lag by minutes, and is idempotent on the order id. - Every point is traceable: earned by which order, under which rule version, consumed by which redemption, or expired on which date. - Seven-year retention of entries - they support audited financial statements. **Out of scope** Tier qualification, campaign design, the reward catalogue’s merchandising, fraud scoring. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps; a schema of 6 tables and 36 columns. ## Data & analytics ### Ad serving - an auction inside a page load - URL: https://tierslate.com/templates/ad-serving - Kind: system architecture board; level: deep - Access: paid, 800 AI credits - the question is public, the worked answer is not Why a system with eighty milliseconds to decide must spend money it cannot yet count. What is hard about it, left in on purpose: THE BUDGET COUNTERS ARE APPROXIMATE ON PURPOSE AND THE ERROR IS DENOMINATED IN MONEY. A consistent decrement across four hundred ad servers cannot fit in an eighty millisecond budget, so each server holds a local allowance and syncs every two hundred milliseconds - which means a campaign near its cap over-delivers by a few tenths of a percent, and over-delivery is served, measured, reported and then WRITTEN OFF, because an advertiser is never billed past their budget. Tightening the sync interval costs latency and infrastructure; loosening it costs revenue directly; and nobody on either side of that argument is wrong. The second unresolved thing is that the auction clears on a PREDICTED click-through rate, so the ranking is only as honest as a model trained on yesterday, and a new creative with no history is priced by a prior that is confidently wrong in whichever direction the prior happens to lean. And the pacer's own input is stale spend, so it is steering a car using a speedometer that reports what was happening two seconds ago - which is fine at a steady pace and oscillates badly whenever traffic moves. #### Start here **Why this design** Ad serving is the clearest case of a latency budget dictating architecture: an auction over two million campaigns has to finish inside about eighty milliseconds of a page load, so nothing on the serving path may block on anything it cannot bound. Interviewers use it to probe whether you can do latency arithmetic, keep databases off a hot path, and reason about counters that cannot be both fast and consistent. Real ad teams live with the consequence every day, because the error in those counters is denominated in money - over-delivery that is served, measured and written off. **What you will learn** - How to spend an 80 ms p95 budget line by line - 9 ms in, 11 ms out, parallel fan-out - and why that leaves no room for a single database read. - Why the campaign index is held in memory on every ad server and accepted as 30 seconds stale, and what a paused campaign that keeps serving costs. - How local budget allowances synced every 200 ms trade over-delivery for latency, and why that interval is a revenue parameter worth tens of millions a year. - How pacing works as a control loop on stale spend, and why it oscillates when traffic moves quickly. - Ranking on bid x predicted CTR with a second-price clear, and the cold-start bias a creative with no history introduces. - Why the spend ledger is the only lossless component, kept off the serving path, with the write-off recorded as a column rather than a difference. **How to read this board** - Diagram, left to right: THE PAGE (the 80 ms clock starts here), ELIGIBILITY (2,000,000 campaigns down to 5,000), PACE & PREDICT (decisions on stale numbers), THE AUCTION (15 ms of arithmetic). - Then AFTER THE PAGE on the right: impressions, the spend ledger, billing and the budget sync wire that carries allowances back. Press play for the walkthrough. - Page 1 · Requirements - the questions to ask first, and the latency and over-delivery targets. - Pages 2 and 3 · Capacity and Storage - the 80 ms budget table, the fleet and counter sizes, and what over-delivery costs in dollars. - Page 4 · Availability - why failing fast is right here, and what degrades rather than dies. - Page 5 · How it works - one ad request end to end, then Page 6 · Deployment - regional budget splits, rollout by revenue per request, rollback. - Finally the DATA MODEL at the bottom: cents as integers, a sampled auction log, and the spend ledger as system of record. #### 1 · Requirements **Ask these before drawing anything** - What is the latency budget, and who enforces it? If the publisher drops the slot at 100 ms, then 100 ms is a correctness requirement and not a performance goal, and every design decision below follows from it. - Is the advertiser billed per impression or per click? Per click means the auction must rank on predicted click-through rate times bid, which introduces a model into the money path and makes prediction quality a revenue number. - What happens when a campaign goes over budget? "It cannot happen" is not an answer at this latency. The real question is how much over-delivery is acceptable and who absorbs it, and it should be a written number. - Must a paused campaign stop immediately? Immediately is expensive. Thirty seconds is nearly free. Somebody has to pick, and they should know they are picking. - Is this a first-price or second-price auction, and are there external demand partners? A real-time bidding call to an external exchange spends half the budget on somebody else's network. - What are the brand safety and legal constraints, and are they hard filters? An ad shown next to the wrong content is not a quality problem, and it must be filtered before the auction, never scored inside it. **Functional** - Fill an ad slot within 80 ms at p95, or return empty rather than late. - Select from roughly two million active campaigns by targeting rules - geography, device, audience segment, context, frequency cap. - Run a second-price auction on expected value: bid times predicted click-through rate, with a floor price. - Pace each campaign so that its daily budget is spent roughly evenly across the day rather than by mid-morning. - Never bill an advertiser more than their budget, even when more than their budget was delivered. - Log every impression and click with the auction that produced it, and reconcile against the spend ledger hourly. - Give the advertiser reporting that is at most a few minutes behind, and a final billable number that is stable once a day closes. **Non-functional** - p95 80 ms, p99 120 ms, measured at the ad server including its dependencies. Any dependency without a timeout is a bug, not a risk. - No component on the serving path may perform an unbounded read. The campaign index is in memory, the counters are local, and the model is co-located. - Over-delivery under 0.5 percent of spend, measured daily. It is never zero, and a target of zero produces a design that under-delivers instead, which is worse. - The spend ledger is the financial system of record and must lose nothing. It is the only component here with that property, and it is deliberately off the serving path. - Availability 99.95 percent for serving. An empty slot is lost revenue for that impression only, which makes this one of the few systems where failing fast is genuinely the right call. **Out of scope** - Creative rendering and viewability measurement, which happen in the browser and have their own unpleasant realities. - Fraud and invalid traffic detection. It is essential, it is a whole system, and it sits between the impression log and billing rather than inside the auction. - Attribution and conversion tracking, which run on an entirely different clock - days, not milliseconds. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 9 steps; a schema of 5 tables and 43 columns. ### Analytics pipeline - the lag between an event and the answer - URL: https://tierslate.com/templates/analytics-pipeline - Kind: system architecture board; level: core - Access: paid, 400 AI credits - the question is public, the worked answer is not Why a click takes ten seconds to reach one dashboard and until tomorrow to reach the one people trust. What is hard about it, left in on purpose: THE SAME QUESTION HAS TWO ANSWERS ON THIS BOARD AND THEY DO NOT AGREE. The stream path answers in ten seconds and is wrong by whatever arrived late; the batch path answers tomorrow and is right; the reconciliation job overwrites one with the other, so a number a person screenshotted at 4 p.m. is a different number at 9 a.m. and nothing tells them. Exactly-once is claimed only at the sink and only because the sink dedups on an idempotency key - every hop before it is at-least-once, and if the SDK retries a timeout it could not distinguish from a failure, the duplicate is real until the sink eats it. And the late-arrival window is a business decision dressed as a config value: 24 hours of lateness accepted means every hourly bucket stays mutable for a day, which is why the rollup table has a revised_at column and why a cached dashboard result can be confidently stale. #### Start here **Why this design** An analytics pipeline looks like plumbing - collect, queue, aggregate, chart - and is really a lesson in two answers to one question: a streaming number that is fast and provisional, and a batch number that is slow and trusted. Interviewers use it to probe whether you understand event time versus processing time, late data, and what "exactly-once" can honestly mean; data teams use the same questions when a dashboard and a finance report disagree. The design decisions are mostly about lag and retention, not throughput. **What you will learn** - Why the SDK batches for up to a second, and how that cuts 9,260 events/s at peak to about 772 HTTP requests/s. - Why the collector acknowledges only after the log write has replicated, and why that is the one rule whose breach loses data silently. - How watermarks, one-minute windows and a 24-hour lateness allowance turn into 36 GB of stream state - and how a product decision shrinks it 24x. - Why exactly-once is a property of the idempotent sink, not of the pipeline, and how an idempotency key makes retries harmless. - How the nightly batch recomputes from the raw lake and overwrites the streaming answer, and why the delta it applies is a metric. - Raw lake versus rollups: what each costs to store and to query, and why the raw events are kept at all. **How to read this board** - Diagram, left to right, following the lag: T+0 · SOMETHING HAPPENS (the SDK batches), T+1 s · INGEST (validate and stamp), T+1 s · BUFFER (the event log, the only durable thing). - Then PROCESS (the stream path, late arrivals and the nightly batch) and SERVE (the OLAP store, query API and the analyst). Press play for the walkthrough. - Page 1 · Requirements - freshness, mutability, duplicates and lateness, asked before anything is drawn. - Pages 2 and 3 · Capacity and Storage - event rates, fleet sizes, raw against rolled-up bytes, and the stream state nobody counts. - Page 4 · Availability - two SLAs for two systems, and the lag monitor that catches what uptime does not. - Page 5 · How it works - one event end to end, then Page 6 · Deployment - regional ingest, central processing, shadow rollouts. - Finally the DATA MODEL at the bottom: event types, raw events, hourly rollups, late events and consumer lag. #### 1 · Requirements **Ask these before drawing anything** - How fresh does the answer have to be, and who says so? "Real time" is never the requirement. The requirement is a person with a decision, and the honest question is how much later than the event that decision may be made. Ten seconds and one hour are different architectures, not different settings. - Is the number allowed to change after it is shown? If yes, the stream path is viable and the reconciliation job below is mandatory. If no, there is only a batch path and this board gets simpler. - What is the cost of a duplicate, and what is the cost of a miss? Double-counting a page view is a rounding error. Double-counting a purchase is a finance incident. The answer decides how much of the exactly-once machinery is worth paying for. - How late may an event be and still count? This single number sets how long every time bucket stays mutable, and therefore how long results cannot be cached, archived or trusted. - Who owns the event schema - the pipeline team or the mobile team that emits it? Whoever owns it decides whether a renamed field is a deploy or an outage. - Are these events personal data? If they are, retention is not a cost decision any more, deletion has to be possible by subject, and immutable object storage becomes a liability. **Functional** - Accept events from web, mobile and server SDKs, at-least-once, with an idempotency key on every event. - Validate each event against a registered schema and reject - loudly, to a quarantine topic - what does not fit. - Keep every raw event exactly as received, before any processing, so that any future question can be asked of the past. - Compute per-minute and per-hour rollups over a dozen dimensions, visible within ten seconds of the event. - Recompute the same rollups nightly from the raw lake and replace the streaming values where they differ. - Accept events up to 24 hours late and fold them into the bucket they belong to, not the bucket they arrived in. - Serve arbitrary time-range queries over the rollups in under 500 ms at p95. **Non-functional** - Ingest availability 99.95 percent. The collector is the only part a client talks to, and a rejected event is gone - the SDK buffers, but only for a while and only on a device that stays open. - End-to-end streaming lag under 10 seconds at p95, under 60 seconds at p99. - No event loss once acknowledged. Acknowledgement happens after the log write is replicated, not before, and that choice costs about 8 ms of collector latency. - The batch answer is authoritative. Where batch and stream disagree, batch wins, and the disagreement is measured rather than assumed. - Cost per event must stay under one tenth of a cent, which at 200 million events a day is what makes retention a design decision rather than a default. **Out of scope** - Session stitching and identity resolution. Both belong upstream of this board and both are harder than anything on it. - Machine learning features. The lake feeds them, but a feature store has different freshness and different correctness requirements. - Sub-second answers. Anything that needs to react inside one second is not analytics, it is the serving path of the product, and it must not be built on this. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 9 steps; a schema of 5 tables and 39 columns. ### Log aggregation - the index is bigger than the data - URL: https://tierslate.com/templates/log-aggregation - Kind: system architecture board; level: core - Access: paid, 400 AI credits - the question is public, the worked answer is not Why searching your logs costs thirteen times more than keeping them, and what you can no longer ask once you sample. What is hard about it, left in on purpose: THE SAMPLER IS THE BEST LEVER IN THE SYSTEM AND IT IS IRREVERSIBLE. Dropping ninety percent of successful requests cuts the bill by eighty-five percent and silently removes the ability to answer "what happened to THIS request" - there is no error, no gap in the UI, and the question simply returns nothing, which is indistinguishable from the request never happening. The second unresolved thing is that log volume rises five to twenty times during an incident, which is exactly when somebody is typing a query, so the indexer is furthest behind at the moment freshness matters most and the honest options are all bad: shed load and lose the incident's own logs, buffer and be minutes stale, or provision twenty times the steady-state indexing fleet and pay for it every quiet day of the year. And the retention policy is written as a cost decision but is enforced as a legal one - a legal hold on a tenant that has already had its shards deleted is a problem no amount of engineering fixes afterwards. #### Start here **Why this design** Log aggregation looks like plumbing and is really a cost-model question. Interviewers use it to see whether you can separate the copy you keep to SEARCH from the copy you keep to READ, size a pipeline for the incident rather than the average day, and say plainly what sampling destroys. Platform teams care because the log bill is routinely one of the largest lines in an infrastructure budget, and because a log pipeline tends to fall behind during exactly the outage it exists to explain. **What you will learn** - Why a 400 B line costs about 520 B fully indexed and 40 B archived - the 13 : 1 ratio every other decision on the board follows from. - Sizing from the fleet: 3,000 instances x 50 lines/s = 13 billion lines and 5.2 TB a day, with 5x to 20x spikes during an incident. - How a 2 GB node buffer and a 24-hour, 48-partition transport queue turn a downstream outage into delay rather than loss - and why backpressure must never reach the application. - What sampling all errors and 1 in 10 successes, per trace, saves (11.8% kept) and which questions it makes permanently unanswerable. - Why "drop the index, keep the bytes" at 7 days turns 2.4 PB a year into 195 TB and a 187x cost difference. - Why search must report the indexing watermark and the shards that did not answer, instead of presenting partial results as complete. **How to read this board** - Press play once: nine steps follow one line from stdout to an engineer's query at three in the morning. - PRODUCE, then COLLECT: structured JSON to stdout, enrichment at the node agent, and the red sampler - the cheapest lever and the only box that destroys evidence. - SHIP · THE BUFFER THAT ABSORBS AN INCIDENT: the disk buffer, the transport queue, and the parser that turns one line into three copies. - INDEX · BIGGER THAN THE DATA IT INDEXES: follow the expensive copy into the inverted index and hot shards, and the cheap one into the cold archive; then rollover drops the index and keeps the bytes. - SEARCH · AT THREE IN THE MORNING: scatter-gather over shards, the stale-index watermark, and log-based alerts. - Pages 1 to 6 in order: requirements, capacity, storage, availability, how it works, deployment. - DATA MODEL: sources, the shape of a line, shards, sampling rules and retention policies - the metadata that decides which lines exist at all. #### 1 · Requirements **Ask these before drawing anything** - What question are the logs for? Debugging one request, auditing who did what, and counting how often something happens are three different systems. The third one should be a metric and using logs for it is the most expensive mistake in this category. - How long must a line be searchable, and how long must it merely EXIST? These are different numbers with a thirteen-to-one cost ratio between them, and treating them as one number is how a log bill becomes the largest line item in an infrastructure budget. - Is any of this subject to audit or legal hold? If yes, deletion stops being a cost optimisation and becomes a compliance operation, with an approval and a record. - Do logs contain personal data or secrets? They do. Everyone says no and everyone is wrong - a request body logged on error, a token in a URL, an email in a stack trace. Whether redaction happens at the agent or never happens is decided here. - How much does volume rise during an incident? Nobody measures this until the first time the log system falls over during an outage. Five to twenty times is the usual answer. - Who pays? If it is a central platform budget, every team logs everything. If it is charged back per team, volume drops by half in a month without a single engineering change. **Functional** - Collect from every process on every node without the application knowing how, and without a slow collector ever blocking the application. - Enrich every line with its service, pod, region, deploy version and trace id at collection time - not at query time, when the pod that produced it no longer exists. - Make a line searchable within 10 seconds of it being written, at p95. - Full-text search plus structured field filters over any time range within the hot window. - Keep an unindexed, compressed copy of EVERY line - including sampled-out ones where the policy allows - for one year, retrievable by time and source. - Apply sampling and drop rules centrally, with a record of what was dropped and by which rule. - Evaluate saved queries as alerts every 60 seconds. **Non-functional** - Ingest must never block a producing application. The agent writes to local disk and a full disk drops lines rather than applying backpressure into the process - a logging system that can stall a production service is worse than no logging system. - Search p95 under 3 seconds over a 24 hour window; under 30 seconds over the full hot window. - Survive a 20x volume spike for 30 minutes without losing a line. This is the requirement that sizes the transport queue and the node buffer, and it is the one that gets cut first and regretted first. - Tenant isolation on every query: one team must not read another team's logs, and the filter is applied by the search API, never by the UI. - Cost per GB ingested is a published number that teams can see against their own volume. **Out of scope** - Metrics. Counting things with a log query works until it costs a hundred times what a counter would and is a hundred times slower. - Distributed tracing. The trace id is carried here so the two systems can be joined, but spans belong somewhere built for them. - Long-term analytics over logs. A year of archive is for answering a specific question about a specific past moment, not for a quarterly report. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 9 steps; a schema of 5 tables and 42 columns. ### Metrics and monitoring - cardinality is the thing that kills you - URL: https://tierslate.com/templates/metrics-monitoring - Kind: system architecture board; level: core - Access: paid, 400 AI credits - the question is public, the worked answer is not Why one label with unbounded values takes down the system that was supposed to explain the outage. What is hard about it, left in on purpose: THE CARDINALITY LIMITER IS DRAWN RED BECAUSE ITS SUCCESS AND ITS FAILURE LOOK IDENTICAL. When a team ships a label with a user id in it, the limiter starts rejecting that tenant's writes - which protects everyone else and means the team instrumenting a new feature sees gaps in a dashboard and assumes their code is broken. There is no good place to put that pain, and a limiter that fails open takes the whole cluster down instead. The retention tiers have the same shape of problem: a five-minute rollup cannot answer a question about a thirty-second spike, so every query beyond fifteen days is quietly answering a different question than the person asked, and nothing in the UI says so. And the alerting floor is the scrape interval - thirty to sixty seconds from condition to page - which no amount of engineering below that layer improves, but which every incident review asks about anyway. #### Start here **Why this design** Metrics and monitoring separates people who have run a time series database from people who have drawn one. Interviewers use it to probe whether you know that the unit of cost is the SERIES and not the sample, that one unbounded label can take the cluster down during the incident it should explain, and that retention tiers trade storage for questions you can no longer ask. Almost every team running Prometheus-style monitoring at scale has had the cardinality outage at least once, which is why this board is built around it. **What you will learn** - Why active series - 3,000 instances x 500 metric names x 6 label sets = 9 million - and not 600,000 samples/s is the number that sizes the system. - How one user_id label takes 9M series to 79M in a week and the index from 31.5 GB to 277 GB of memory, with every sample valid and the disk barely moving. - Why the limiter caps ACTIVE SERIES per tenant, fails closed, and still looks like a bug to the team it rejects. - How delta-of-delta and XOR encoding take a 16 B sample to 1.7 B, and how 15 s / 5 min / 1 h tiers make two years of storage 19.6x cheaper. - What a downsample can no longer answer: the shape of a spike, and any p99 built by averaging p99s. - Why alert latency has a floor of scrape plus evaluation interval, so a `for: 2m` rule pages two and a half to three minutes after the condition. **How to read this board** - Press play once: nine steps follow one sample from a counter in process memory to a page on somebody's phone. - PRODUCE, then SCRAPE & ADMIT: the 15 s scrape, the ingest gateway, and the red cardinality limiter whose refusal wire goes backwards. - WRITE PATH · MEMORY FIRST: write-ahead log, head block, and the red inverted label index - the part that actually breaks. - RETENTION TIERS · THE COST MODEL: raw blocks, the compactor, the 5-minute and 1-hour tiers. - SERVE & ALERT: the query frontend's per-query series limit, the result cache, and the rule evaluator. - Pages 1 to 6 in order: requirements, capacity, storage, availability, how it works, deployment. - DATA MODEL: metric names, label names, series, retention tiers and cardinality incidents - what a series costs, and who added it. #### 1 · Requirements **Ask these before drawing anything** - How many machines, processes and containers, and how fast does that number change? A fleet that redeploys twenty times a day creates a new pod id every time, and if pod id is a label, every deploy is a cardinality event. - Who is allowed to define a metric, and does anyone review it? If any engineer can add a label in a pull request nobody from this team reads, the limiter is not a safeguard, it is the review process. - What is the alerting latency anybody actually needs? The floor is the scrape interval plus the evaluation interval. Asking for five seconds means scraping every five seconds, which is four times everything. - How far back do people query, and at what resolution? "A year" and "a year at 15-second resolution" differ by a factor of two hundred and forty in storage. - Is this multi-tenant? If yes, one tenant can take out every other tenant, and per-tenant limits stop being nice-to-have. - Are these metrics or are they events? If somebody wants to know which user hit the error, they want logs or traces, not metrics, and giving them a label is how the system dies. **Functional** - Scrape every instance every 15 seconds and store every sample with its timestamp and its full label set. - Answer range queries over any time window with aggregation, rate and quantile functions. - Evaluate alerting rules every 30 seconds and notify when a condition has held for a stated duration. - Downsample automatically into a 5-minute and a 1-hour tier and expire the raw samples on schedule. - Enforce a per-tenant active series limit, reject over it, and make the rejection visible to the tenant rather than only to the platform team. - Let anyone see the cardinality of their own metrics, by metric and by label, without asking the platform team. **Non-functional** - The monitoring system must be more available than the systems it monitors - 99.9 percent - and must not share their dependencies. A metrics stack that runs on the cluster it watches is blind during exactly the outage that matters. - Query p95 under 1 second for a 6 hour window, under 10 seconds for a 30 day window. - Ingest must not be affected by query load. One expensive query must not delay a write, which is why the read and write paths are separate fleets rather than one process. - An alert fires within 60 seconds of its condition becoming true, and that number is the scrape interval plus the evaluation interval, not a target anyone can tune. - No single tenant may consume more than 10 percent of cluster memory, enforced rather than requested. **Out of scope** - Logs and traces. They answer "which request" and metrics answer "how many", and the most expensive mistake in this whole category is using one for the other. - Long-term analytics over metrics. Two years of hourly rollups is for capacity planning, not for a business report. - Anomaly detection. Worth having, worth building second, and it changes none of the arithmetic here. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 9 steps; a schema of 5 tables and 37 columns. ### Recommendation engine - retrieve a thousand, rank twenty - URL: https://tierslate.com/templates/recommendation-engine - Kind: system architecture board; level: deep - Access: paid, 800 AI credits - the question is public, the worked answer is not Why the feed is computed in a hundred milliseconds by a model that is a day out of date. What is hard about it, left in on purpose: THE OFFLINE EVALUATION AND THE ONLINE RESULT ARE MEASURING DIFFERENT SYSTEMS AND THE BOARD DOES NOT FIX IT. Training features come from a warehouse query; serving features come from a key-value store filled by a stream, written by different people in a different language - so a model that scored better offline routinely does nothing or does harm online, and the usual gap is a few percent of feature values, which is enough. The second unresolved thing is the FEEDBACK LOOP: the model is trained on clicks that the previous model chose to show, so an item that was never shown can never be learned from, and popularity compounds into a monoculture that every offline metric will happily call an improvement. And cold start has no good answer here - a new item falls back to trending, which is exactly the signal that buries new items, so the system is structurally biased against the content it most needs to evaluate. #### Start here **Why this design** "Design a recommendation feed" is the standard question for ML-adjacent system design because it cannot be answered with one model or one database. Ten million items cannot be scored inside a 100 ms request, so the answer is always a funnel - cheap recall, expensive ranking, rules on top - fed by a training loop that runs on a clock measured in hours. Interviewers use it to see whether you size the funnel from the latency budget and whether you know where these systems actually fail in production: training/serving skew and a feedback loop that trains on its own output. **What you will learn** - Why the work is split into retrieval (10,000,000 -> 1,000 in about 5 ms) and ranking (1,000 -> 20 in about 25 ms): scoring the whole catalogue with the ranker would take 250 seconds. - How to build a 100 ms p95 budget line by line - candidate sources in parallel, 400,000 feature values in 8 batched reads, ranking, blending - and where the tail goes. - Why at least three independent candidate sources (ANN, co-visitation, trending) make a failure degrade quality instead of emptying the feed. - Why eligibility rules (blocked, region-locked, already bought) belong before the model, and editorial overrides after it. - Why features must be logged as served, how training/serving skew silently invalidates offline wins, and how the nightly loop turns clicks into the next model. **How to read this board** - Press Play and follow the walkthrough left to right across the diagram: THE REQUEST (100 ms, starting now), RETRIEVE, RANK, then BLEND. - Then THE LOOP on the right - the nineteen-hour path from a logged impression back to a promoted model. - 1 · Requirements - what is being optimised, and what must never be recommended. - 2 · Capacity estimation - the latency budget is the real capacity constraint - then 3 · Storage estimation. - 4 · Availability - why the feed must never be empty and what every failure degrades to. - 5 · How it works, then 6 · Deployment plan. - Finally the DATA MODEL at the bottom, then argue with what the board leaves unresolved on purpose: offline and online computing the same feature twice, a model trained on what the last model chose to show, and a cold start that falls back to the signal that buries new items. #### 1 · Requirements **Ask these before drawing anything** - What is being optimised, in one sentence, and who agreed to it? Clicks, watch time, purchases and long-term retention give four different systems, and optimising the easy one usually damages the important one. - How big is the catalogue and how fast does it change? Ten thousand items that change monthly needs none of this. Ten million that change hourly needs all of it, and the difference is entirely in candidate generation. - How fresh must a recommendation be? If a purchase must change the next page, that is a session-level requirement and needs real-time features. If tomorrow is fine, the whole right-hand side of this board can be a nightly batch and cost a tenth as much. - What must NEVER be recommended? Age-restricted, region-locked, out of stock, already bought, blocked by the viewer. These are correctness rules and they belong before the model, not inside it. - Is there an editorial or commercial override? There always is eventually, and building it in from the start is much cheaper than retrofitting a rules engine around a model. - How will this be evaluated? If the answer is offline AUC only, the system will ship regressions confidently. If there is no experiment framework, there is no recommendation system - only a model. **Functional** - Return 30 ranked items for any viewer within 100 ms at p95, including a viewer seen for the first time. - Retrieve candidates from at least three independent sources so that the failure of one degrades quality rather than emptying the feed. - Exclude what the viewer has already seen in this session and what they have interacted with recently, unless the item type says otherwise. - Log every impression with its position, the model version that produced it and the feature values used - without this the training data is unusable and every experiment is unattributable. - Retrain nightly and promote a model only after an offline evaluation and a shadow run on real traffic. - Support holding a percentage of traffic on the previous model, sticky per viewer, for as long as an experiment runs. **Non-functional** - p95 end-to-end 100 ms, p99 150 ms. Past that the app shows a fallback, because a slow feed is worse than a generic one. - The feed must never be empty. Every failure mode degrades to trending, and trending is served from a cache that is refreshed independently of everything else on this board. - Feature skew between offline and online computation must be measured continuously and kept under 1 percent per feature. Unmeasured skew is the default state and it silently invalidates every offline result. - Model rollback must take under one minute and must not require a deploy. - No viewer-level data crosses a region boundary on the serving path. **Out of scope** - Search. A query is an explicit intent and deserves a different retrieval path, even though it shares this index. - Ads. The auction has a different objective and a hard budget constraint, and mixing the two objectives into one score is how both get worse. - Explanations. Worth having, and honest ones are much harder than they look once the ranker is a gradient-boosted ensemble of four hundred features. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 9 steps; a schema of 5 tables and 41 columns. ### Leaderboard - a sorted set and the rank nobody stores - URL: https://tierslate.com/templates/leaderboard - Kind: system architecture board; level: starter - Access: free - everything below is the whole board How a sorted set answers top ten and my rank in milliseconds, and why rank is computed rather than stored. What is hard about it, left in on purpose: The whole global board is one sorted-set key on one Redis shard: every ZADD and every ZREVRANK in the world lands on one single-threaded node, so it cannot be scaled out by adding shards without making rank a sum across shards. Redis replicates asynchronously, so a failover silently forgets the last second of score updates until the rebuild job replays the log - and until it finishes, some players see a rank that is wrong. Ties are broken by packing time into the score, which only works while the score fits in a double's 53 bits. #### Start here **Why this design** A leaderboard looks like a SELECT ... ORDER BY score DESC LIMIT 10, and for a few thousand players it is. It stops being one when two questions have to be answered for millions of players at once: who is in the top ten, and what is MY rank. The first is easy with an index. The second is the trap - "count everyone above me" over a B-tree is a scan, and storing rank in a column means one good game rewrites most of the table. The answer is a structure that keeps members in order AND knows how many sit between any two points: a sorted set. **What you will learn** - Why rank is computed at read time, never stored, and the one exception (a frozen season). - How a sorted set (skip list + hash) gives O(log N) insert, O(log N) rank and O(log N + 10) top ten. - That 50M players is about 5 GB in a sorted set and 100M updates a day is ~1,200/s - one Redis node handles both. - Why the sorted set is an index you can rebuild, not the source of truth, and what that costs on failover. - How ties are broken by packing time into the score, and where that trick runs out. **How to read this board** - Top: SCORE PATH. A player finishes a match, the game server reports it, the score service records it durably and publishes an event. - Right: STORES. The score store (truth) and the red sorted set (the index that answers ranks). - Bottom: READ PATH. A player opens the board; the leaderboard API asks the sorted set and decorates ten ids with names. - Bottom right: JOBS. The rebuild job and the season snapshot - the two things that exist because the sorted set can be lost. - Press play for the walkthrough, then read pages 1 to 6, then the tables at the bottom. - The conversation to have is the red box: one key, one shard. Page 5 says what you would do when one node is no longer enough. #### 1 · Requirements **Functional** - Submit a score at the end of a match. A player's board score is their BEST score this season (or a running total - pick one; this board uses best). - Show the top 10 (and top 100) for the global board. - Show my rank and score, and the 5 players directly above and below me ("around me"). - Boards per season. A season ends, its final ranks are frozen and prizes are paid from that snapshot. - Display names and avatars beside each entry. **Non-functional** - Top ten and my rank: p99 under 50 ms. My rank appears on the end-of-match screen, so it is on the critical path of every game. - A new score is visible on the board within 1 to 2 seconds. - Scores are durable: an accepted match result is never lost. The ranking index may be rebuilt. - Ranks are exact for the top 10,000. Below that, "top 12%" is an acceptable answer if exact becomes expensive. - Score submission is authoritative from the game server, never the client, or the top ten fills with cheaters. **Out of scope** - Friends-only boards (a small per-user sorted set, or a sort at read time over a few hundred friends). - Anti-cheat beyond "only the game server may submit". - Per-region boards - the same design with one key per region. #### 2 · Capacity estimation **Assumptions** Players on the all-time board 50,000,000 Daily active players 10,000,000 Matches per player per day 10 Board opens per player per day 3 Peak factor x5 **Writes** Score submissions 10M x 10 = 100,000,000 / day / 86,400 = 1,160 /s average x 5 = ~5,800 /s peak **Reads** My rank on the results screen = 1 per match = 1,160 /s avg, 5,800 /s peak Board opens 10M x 3 = 30M/day = 350 /s avg, 1,750 /s peak each = ZREVRANGE 0 9 + ZREVRANK + around-me range Total sorted-set commands at peak ~ 5,800 + 1,750 x 3 ~ 11,000 /s A single Redis node does well over 100,000 simple commands a second, and these are O(log N) with log2(50M) ≈ 26 levels. So the sorted set is not a throughput problem today - it is a single-node problem: there is exactly one of it. Throughput would become the problem at roughly 10x this traffic. **Why not SQL** SELECT count(*) FROM best_scores WHERE score > :mine rank 25,000,000 = scan 25M index entries ~ seconds per query, x 5,800 /s = impossible A B-tree finds a score fast but does not know how many entries sit before it. A skip list in a sorted set stores the span of every link, so it counts while it walks. #### 3 · Storage estimation **The sorted set** member player id, 16 B score 8 B double skip list node + hash entry + pointers ~ 70 B ~ 100 B per member 50M members x 100 B = ~5 GB one key per season, keep current + previous = ~10 GB Ten gigabytes fits in memory on one node with room to spare, which is why the board can draw one. It is also why it is tempting never to split it. **The score store (truth)** score_events row ~ 80 B (player, season, points, time) 100M / day x 80 B = 8 GB / day keep 90 days = 720 GB, then roll up or archive best_scores row ~ 60 B 50M players = 3 GB per season **Tie-breaking inside one double** score = points x 2^32 + (2^32 - 1 - seconds_since_season_start) earlier achiever ranks higher at equal points a double is exact to 2^53, so points must stay under 2^21 = 2,097,152 points - check the game can never exceed it Redis orders equal scores by member name, which is arbitrary. Packing time into the low bits makes ties deterministic and fair, at the cost of a hard ceiling on the score. #### 4 · Availability **Targets** - Score submission: 99.95 percent. A lost result is a support ticket. - Rank reads: 99.9 percent, degrading to "rank unavailable" rather than a wrong number. **What fails, and what it costs** - The sorted set node (red). Replicated to a standby asynchronously. On failover the standby may be missing the last second of ZADDs - those players see their old best. The fix is that the score service never relied on Redis for durability: the rebuild job replays score events from the last checkpoint and the board heals in seconds to minutes. - Losing the sorted set entirely. Rebuild from best_scores: read 50M rows, ZADD in batches of 1,000 - about 50,000 pipelined batches, a few minutes. During that window serve the top ten from a cached copy and hide "my rank". - The score store. The durable part; a primary with a synchronous standby. If it is down, submissions are queued at the game server and retried - the match result is held, not dropped. - The event log. If the rank updater lags, the board is stale, not wrong. Alert on consumer lag above 5 seconds. **Degrade, do not lie** A rank that is stale by two seconds is fine. A rank that is wrong after a failover and stays wrong is not. That is why the updater writes with ZADD GT (only raise a score), which makes replaying the same events twice harmless. #### 5 · How it works **Score path** The match ends on the game server, which is the only thing allowed to report a score. The score service inserts the result into score_events, upserts best_scores if it beat the old best, and publishes a score event. The rank updater consumes events and runs ZADD season:42 GT . GT means the stored score only ever goes up, so a replayed or out-of-order event cannot lower anyone. **Read path** - Top ten: ZREVRANGE season:42 0 9 WITHSCORES - O(log N + 10). - My rank: ZREVRANK season:42 - O(log N); add 1 for a human rank. - Around me: ZREVRANGE season:42 rank-5 rank+5. - Then one multi-get of ten display names from the profile cache. The sorted set holds ids only, so a rename never touches it. **Why rank is never stored** Rank is a function of everyone else. If player A jumps from 900,000th to 10th, every player from 10th to 899,999th drops by one. A rank column turns one score update into up to N row updates; a sorted set turns it into one O(log N) insert and computes rank on the way down the skip list. The one time a rank IS written is when it stops changing: the season snapshot copies the final order into season_final_ranks so prizes are paid from a fixed list. **When one node is not enough** - Read replicas for ZREVRANGE / ZREVRANK - easy, slightly stale. - Split by score range (shard 1 holds scores above X ...). My rank = my rank in my shard + the sizes (ZCARD) of all higher shards. Exact, but boundaries must move as scores grow. - Exact rank for the top 10,000, and a percentile from a histogram of score buckets for everyone else. Most games do this. #### 6 · Deployment plan **Topology** - Score service and leaderboard API: stateless, several replicas behind a load balancer, one region near the game servers. - Redis: one primary per season key with one replica in another zone, persistence on (AOF every second) to shorten rebuilds. - Postgres: primary with a synchronous standby for score_events and best_scores. - Event log: a partitioned log (Kafka or similar) keyed by player id, so one player's events stay in order. **Rollout** - Build the new season key before the season starts; flip the API to it by config at the season boundary. - Change the score packing only with a new key, never in place - old and new scores do not compare. - Canary the rank updater by running it into a shadow key and diffing the top 1,000 against the live key. **What to watch** Redis CPU (it is one thread), command latency p99, memory of the season key, rank updater consumer lag, and the count gap between ZCARD season:42 and the row count of best_scores for that season - if they drift apart, the index is missing players. #### Walkthrough 1. **A match ends** - A player finishes a match. The game server, not the client, reports the score - a client that could submit its own number would own the top ten by lunchtime. 2. **Record the score durably** - The score service appends the result to score_events and raises best_scores if this beat the old best. This is the truth. Nothing about rank is written here. 3. **Publish a score event** - The service publishes an event and answers the game server. Ranking happens a moment later, off this request. 4. **One ZADD, and every rank moves** - The rank updater runs ZADD GT on the season key. One O(log N) insert, and implicitly every player below has dropped a place - without a single one of their rows being touched. That is why rank is never stored. 5. **Somebody opens the board** - A player opens the leaderboard tab or reaches the results screen. The client asks for the top ten and for its own rank. 6. **Rank is counted at read time** - ZREVRANGE 0 9 returns the top ten; ZREVRANK counts how many members sit above this player by walking the skip list - about 26 levels for 50M players. Both land on the same single key on the same single node. 7. **Ids become names** - The sorted set holds only ids. Ten names and avatars come from a cache in one multi-get, so renaming yourself never touches the ranking. 8. **The index can be rebuilt** - If the sorted set node fails over and forgets its last second, or dies outright, the rebuild job refills it from best_scores. That is the price of the red box: one node, asynchronously replicated. At the end of a season, the snapshot writes the final order down - the only time a rank is ever stored. #### Schema - players (Who a member id in the sorted set is. Names live here, not in the sorted set, so a rename touches one row.): player_id uuid PK, display_name varchar, country_code varchar, created_at timestamptz - seasons (One sorted-set key per season: season:.): season_id integer PK, name varchar, starts_at timestamptz, ends_at timestamptz - score_events (Every accepted match result, append only. The log the rebuild job can replay.): event_id uuid PK, player_id uuid, season_id integer, points integer, played_at timestamptz - best_scores (One row per player per season. Note what is missing: there is no rank column.): season_id integer PK, player_id uuid PK, best_points integer, achieved_at timestamptz - season_final_ranks (The only stored rank on the board - written once, when the season ends and ranks stop changing.): season_id integer PK, final_rank integer PK, player_id uuid, best_points integer ### Search autocomplete - an answer before the word is finished - URL: https://tierslate.com/templates/typeahead-autocomplete - Kind: system architecture board; level: starter - Access: paid, 200 AI credits - the question is public, the worked answer is not Why suggestions come from a precomputed prefix index, and why freshness is traded for speed on every keystroke. What is hard about it, left in on purpose: Every suggestion comes from an index built once an hour from yesterday's and today's counts, so a query that starts trending now - breaking news, a product launch - is invisible for up to an hour plus the CDN TTL, and there is no fast trending layer to fill the gap. The top ten per prefix is fixed at build time, so short prefixes are owned forever by the same head queries and the long tail never surfaces. And the blocklist is applied at build time only: an offensive suggestion found at 10:05 stays live until the 11:00 build unless someone hot-patches every node. #### Start here **Why this design** Autocomplete is the search box finishing your sentence. It feels like a small feature and it is one of the highest-traffic endpoints a search product has, because it fires on keystrokes rather than on searches - several requests for every query anybody types. The interview question is really about one decision: what do you compute per keystroke, and what do you compute in advance? The answer on this board is "nothing" and "everything". **What you will learn** - Why 500M searches a day become ~3 billion suggestion requests, and why the client debounce is part of the capacity plan. - How a precomputed prefix -> top-10 table turns each keystroke into one in-memory lookup. - Why the whole index fits in memory on every serving node (~12 GB), so there is no shard to call. - How ranking is decided offline from counted, time-decayed, filtered query logs. - What freshness you give up, and the usual fix: a small real-time trending layer merged on top. **How to read this board** - Top: ONE KEYSTROKE. User, search box, CDN edge, the suggest service hex and its in-memory prefix index. - Right: SHARED STORES. Query counts and the versioned index snapshots the serving nodes load. - Bottom: OFFLINE PIPELINE. Finished searches flow into a log, get counted hourly, and a builder writes a new index. - Press play for the walkthrough, then read pages 1 to 6, then the tables. - The conversation to have is the red builder: everything a user sees is at most as fresh as its last run. #### 1 · Requirements **Functional** - As the user types, show up to 10 completions of the current prefix, best first. - Rank by popularity, with recent searches weighted more than old ones. - Never suggest blocked terms (abuse, slurs, legal takedowns). - Work from the first character; accept lower case, trimmed, accent-folded input. - Selecting a suggestion runs the full search. **Non-functional** - Server p99 under 20 ms; perceived end-to-end under ~100 ms, or suggestions arrive after the user has typed past them. - Availability 99.95 percent, and failure is silent: no suggestions is acceptable, an error box is not. - Freshness: a new popular query appears within about an hour. - Suggestions are the same for everyone with the same prefix and language - which is what makes them cacheable. **Out of scope** - Personalised suggestions (your own history) - a small per-user list merged on the client. - Spelling correction and fuzzy matching ("pyhton" -> python). - Full search ranking; this board only feeds the box. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps; a schema of 4 tables and 18 columns. ### View counter - counting a million clicks without a million writes - URL: https://tierslate.com/templates/view-counter - Kind: system architecture board; level: starter - Access: paid, 200 AI credits - the question is public, the worked answer is not Why a counter on a hot row is batched in memory, and how much of the count you may lose on a crash. What is hard about it, left in on purpose: Views are summed in each API node's memory and flushed every 5 seconds, so a node that crashes or is killed during a deploy takes up to 5 seconds of its views with it - about 3,500 views per node at peak, 5,000 for a single viral video - and nothing replays them, because the raw event stream is fire-and-forget too and is never reconciled against the counter. The dedupe window lives in one Redis; if it is down the API either counts every refresh or drops every view, and the board has not decided which. #### Start here **Why this design** Every video, post and article shows a number under it. Writing that number looks like one UPDATE per view, and the interview question is what goes wrong when one item gets 50,000 views a second. The answer is not "the database is too slow" - it is that every increment of the same row waits for the same row lock, so the hottest item is limited to a few thousand increments a second no matter how big the database is. The fix is to stop writing per view, and to be honest about what that costs. **What you will learn** - Why a hot row serialises writes, and why adding database replicas does nothing for it. - How in-memory batching turns 50,000 increments a second on one row into 10. - Exactly how many views a crash can lose, computed from the flush window. - Why dedupe (one view per viewer per 30 seconds) is a separate, cheap store. - Why the displayed counter and the analytics numbers are two different pipelines. **How to read this board** - Top: COUNT A VIEW. Viewer, player, load balancer, the View API hex, the dedupe store and the red in-memory batcher. - Right: STORES. The counts table the batcher flushes into, and the count cache the read path uses. - Bottom: READ THE COUNT. The page asks for the number; it comes from cache, a few seconds stale. - Bottom right: ANALYTICS. Raw view events, a rollup job, and daily numbers per video. - Press play, then read pages 1 to 6, then the tables. The conversation to have is the red box. #### 1 · Requirements **Functional** - Count a view when a video starts playing (or after 3 seconds of play - a product rule, decide it once). - Count at most one view per viewer per video per 30 seconds, so refresh-spamming does not inflate counts. - Show the view count on the video page and in listings. - Daily views per video for the creator dashboard. **Non-functional** - Recording a view must never slow playback: the beacon answers in under 10 ms and the player does not wait for it. - The displayed count may lag reality by up to ~10 seconds. - The displayed count may undercount by a small, bounded amount after a crash. It must never overcount. - Handle 50,000 views a second on a single video without degrading other videos. - If views are ever billable (paying creators per view), this board is not enough - see page 5. **Out of scope** - Bot and fraud filtering beyond the dedupe window. - Watch time, likes and other counters - the same design repeated. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps; a schema of 4 tables and 16 columns. ### Uptime monitor and status page - checking from outside - URL: https://tierslate.com/templates/status-page - Kind: system architecture board; level: starter - Access: paid, 200 AI credits - the question is public, the worked answer is not Why uptime is measured from many regions outside your network, and why the status page must not share its fate. What is hard about it, left in on purpose: The probes can only find failures they were written to look for: a /health endpoint that returns 200 while checkout throws on every order is "up" here, so the board measures reachability and calls it availability. The 2-of-3 quorum that suppresses false alarms also suppresses real single-region outages - if eu-west cannot reach you, every European customer is down and the board stays green. All three probes run in cloud data centres, so an outage at a consumer ISP or one CDN point of presence is invisible to them. And the status page is only as honest as the incident engine allows: it posts "investigating" automatically, but every later update waits on an on-call engineer who is busy fixing the outage, which is why public pages are routinely 20 to 40 minutes behind reality. #### Start here **Why this design** Every company with customers eventually needs two things: to find out it is down before customers tell it, and somewhere to say so that keeps working while it is down. Both sound trivial - curl the site every minute, host a page - and both fail in instructive ways. The monitor fails by measuring from the wrong place or by crying wolf; the status page fails by sharing infrastructure with the thing it reports on. This board is about where to stand when you measure, how to decide a failure is real, and how to keep the message up when everything else is not. **What you will learn** - Why internal health checks miss the outages customers see, and what an outside-in synthetic check actually exercises: DNS, TLS, edge, application. - Quorum across regions and consecutive failures: the arithmetic that trades detection time against false pages. - Fate sharing: why the status page runs on a different provider, CDN, DNS and domain. - Why the status page is static, and how a 100x read spike during an incident becomes a non-event. - Who watches the watcher: a dead-man's switch for the monitor itself. **How to read this board** - Press Play and follow the walkthrough: CONTROL PLANE top left, PROBES in the middle, YOUR SERVICE on the right. - Then DECIDE under the probes, where three opinions become one verdict. - Then RESPOND on the left and the STATUS PAGE band on the right - note it shares nothing with YOUR SERVICE. - 1 · Requirements to 6 · Deployment in the written band, then the DATA MODEL. - Then argue with the tension: the board measures reachability and calls it availability. #### 1 · Requirements **Ask these before drawing anything** - What does "up" mean for this product? A 200 from the homepage, or a user able to log in and load their data? The answer decides whether checks are pings or scripted journeys. - How fast must we know? Detection time is roughly interval times consecutive failures needed. Thirty second checks needing two failures is about a minute. - How much does a false page cost? A team paged at 3am for a probe network blip twice a week will start ignoring pages, which is worse than no monitor. - Who writes on the status page, and is anything automatic? Auto-posting "investigating" is fast and occasionally wrong in public. - Is the status page a contract? If an SLA credit depends on it, its history must be accurate and immutable. **Functional** - Define monitors: URL or scripted check, interval, regions, assertions (status code, body contains, TLS expiry over 14 days, latency under a threshold). - Run every monitor from at least three regions outside our cloud account. - Open an incident when a quorum of regions fails on consecutive checks; resolve it when they recover. - Page on-call; post an automatic "investigating" on the affected components; let a human write updates. - Publish a public status page with components, current incidents and 90 days of history, and email or webhook subscribers. **Non-functional** - Detection: a full outage opens an incident within 90 seconds. - False pages: fewer than one per month per service from probe-side causes. - The status page is available at 99.99 percent and is independent of every component it reports on. - The page serves 5,000 requests a second during an incident with no origin scaling. - If the monitor itself stops producing results, someone is paged within five minutes. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps; a schema of 6 tables and 41 columns. ### A/B testing platform - assignment is the easy half - URL: https://tierslate.com/templates/ab-testing - Kind: system architecture board; level: core - Access: paid, 400 AI credits - the question is public, the worked answer is not How users are bucketed deterministically, and why exposure logging and metric pipelines decide whether a result is real. What is hard about it, left in on purpose: EXPOSURE IS LOGGED BY THE CLIENT, SO THE DENOMINATOR DEPENDS ON THE VARIANT. The SDK fires an exposure event when the app first uses a variant, over the same flaky mobile connection as everything else, and about 2% of those events never arrive. That is harmless if both arms lose 2%. It is fatal if they do not: a variant B checkout that is 300 ms slower to render loses more exposures from users who give up before the event flushes, so B’s denominator is missing exactly its worst sessions and B looks better than it is. The SRM check is the only defence on this board, and it only DETECTS the problem - it tells you a two-million-user experiment is ruined after two weeks, it does not tell you which arm lied or let you salvage it. Server-side exposure logging at the decision point would fix most of it and is not drawn. The second weakness is human: results recompute daily with fixed-horizon p-values beside the sequential bounds, and an analyst who checks every morning and ships the first day p < 0.05 has a false positive rate between 20% and 30%, not 5%. The stats engine shows the right number; nothing stops people reading the wrong one. #### Start here **Why this design** Every product team that ships changes and wants to know whether they helped needs an experimentation platform, and the first version is usually a random number stored per user and a dashboard counting purchases per arm. Interviewers ask this to see whether you know that deterministic hashing makes assignment trivially scalable, and - far more importantly - whether you understand what makes a result trustworthy: exposure at point of use, events after exposure, user-level aggregation, sample ratio checks and honest handling of peeking. Data teams that run these platforms spend almost none of their time on bucketing and almost all of it on the metric pipeline. **What you will learn** - Why hash(salt + user_id) mod 10,000 replaces a stored assignment, and how salts and layers keep experiments independent or exclusive. - Why exposure is logged when a variant is used, and how first-exposure is computed. - Why the metric pipeline joins events to exposure by time and aggregates per user before comparing arms. - What a sample ratio mismatch is, why it is checked first, and the worked chi-square. - Sample size: why detecting a 2% lift on a 4% conversion rate needs about 940,000 users per arm. - The honest weakness: client-side exposure loss that depends on the variant, and daily peeking. **How to read this board** - Diagram, left to right: ASSIGN (a hash, not a lookup), LOG (exposure is the denominator), COMPUTE (join, then aggregate per user), DECIDE (is it real?). Press play for the walkthrough. - Below it, GUARDRAILS: the hourly path that kills a harmful experiment before the daily stats would. - Then the DATA MODEL: six tables, two of them with composite keys that ARE the definition of a unit. - Pages 1-3 · Requirements, Capacity and Storage: 3 billion events a day and 120 experiments live. - Page 4 · Availability - why assignment can never be down, and metrics may be a day late. - Page 5 · How it works, then Page 6 · Deployment, where the platform’s own changes are A/A tested. #### 1 · Requirements The platform lets product teams define an experiment, split traffic between variants, and get an answer to "did B change metric X, by how much, and how sure are we?" It is two systems joined by a log: an assignment system in every request path, and an analysis system that runs on yesterday’s data. **Ask these before designing** - What is the unit? Logged-in user, device, session, or account? Bucketing by device splits one person across arms on phone and laptop; bucketing by account is right for B2B pricing. - Can experiments overlap? Independent experiments use different salts and overlap freely. Experiments that touch the same surface go in a layer, share a salt, and take disjoint bucket ranges, so no user is in both. - Where is the decision made - client or server? Server-side decisions can log exposure reliably at the decision point. Client-side ones depend on a phone flushing an event. - How fast must a harmful experiment be stopped? Daily stats are for decisions. A crash-rate guardrail needs to be hourly. - Will people peek? Yes. The question is whether the statistics are built for it (sequential tests) or pretend otherwise. **Functional** - Define experiments: variants, allocations, targeting, primary and guardrail metrics. - Assign deterministically in every SDK, with no network call. - Log first exposure per unit per experiment. - Compute per-unit metrics from product events after exposure, for every running experiment, daily. - Check sample ratio; compute effect sizes with confidence intervals and sequential bounds. - Stop an experiment immediately by setting its allocation to zero. **Non-functional** - Assignment adds under 1 ms and has no failure mode - if config cannot load, every user gets control. - Results by 09:00 for yesterday, for all running experiments. - Guardrails within 1 hour. - Reproducible: any result can be recomputed from the lake and metric definition version. **Out of scope** Feature flags without analysis, multi-armed bandits, causal inference on observational data. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps; a schema of 6 tables and 41 columns. ### ETL and the data warehouse - yesterday, correctly - URL: https://tierslate.com/templates/etl-warehouse - Kind: system architecture board; level: core - Access: paid, 400 AI credits - the question is public, the worked answer is not Why batch loads trade freshness for correctness, and how late data and reruns are made safe. What is hard about it, left in on purpose: THE EXTRACT TRUSTS updated_at, AND updated_at CANNOT SEE TWO THINGS. A HARD DELETE leaves no row to have an updated_at: an order the source deletes (a fraud purge, a GDPR erasure, a cleanup script) stays in the warehouse forever, counted in revenue, and no check on this board compares the set of keys - the row-count check compares rows CHANGED, which a delete does not produce. And the three-day lookback is a bet, not a guarantee: a row whose updated_at is set inside a transaction that commits more than three days later, or set by an application server whose clock is wrong, or changed by a migration that bypassed the trigger, falls outside every future window and is silently never extracted. The board picks a lookback that catches the 99.99% case and reprocesses four days of changes every night to do it - 240 M rows, 96 GB off the replica - and still has no path for the 0.01%. Change data capture from the write-ahead log would see both deletes and late commits, and is deliberately not drawn, because it trades this problem for an always-on streaming dependency. #### Start here **Why this design** Every company past a certain size has a warehouse that finance, product and leadership read every morning, fed by nightly jobs pulling from the production databases and a dozen SaaS tools. The naive version runs "SELECT everything new since last time" and appends it, and within a month it has duplicates from a retried job, gaps from a row that committed late, and a revenue number that disagrees with the billing system. Interviewers use this question to see whether you design a run as an idempotent function of a date, whether you know why loads overwrite partitions instead of appending, how late-arriving data is handled, and whether quality checks gate publication. Data engineers will recognise every failure on this board from an incident. **What you will learn** - Why a run is keyed by a logical date, and rerunning it any number of times produces the same partitions. - Why extraction uses a window that reaches back past the watermark, and what the lookback costs: four days of changes re-read every night. - Why raw extracts are immutable per run, and transforms can be replayed from them. - How a slowly changing dimension (type 2) keeps "the customer’s country at the time of the order". - Why partition overwrite makes retries safe and append makes them duplicates. - What the quality gate checks, and why a failed check blocks the swap instead of logging a warning. - The honest weakness: updated_at cannot see hard deletes or anything older than the lookback. **How to read this board** - Diagram, left to right: EXTRACT (a window on somebody else’s database), ORCHESTRATE (one run per logical date), TRANSFORM (dimensions first, then facts, then the gate), SERVE (the warehouse). Press play for the walkthrough. - Below it, BACKFILL: the same DAG pointed at old dates. - Then the DATA MODEL: a star schema and the run bookkeeping. - Pages 1-3 · Requirements, Capacity and Storage: 60 M changed rows a day, read four times over. - Page 4 · Availability - the 07:00 deadline and the four hours of slack that make one full retry possible. - Page 5 · How it works, then Page 6 · Deployment, where a transform change is a backfill. #### 1 · Requirements The warehouse is the analytical copy of the business: orders, customers, products, payments, marketing spend, modelled for questions like "revenue by region by week" rather than for serving a website. It is fed in batch from systems it does not own. **Ask these before designing** - How fresh must it be? "Yesterday by 07:00" is a batch warehouse. "Within five minutes" is a streaming system with different failure modes; do not build that by running batch every five minutes. - Does the source hard-delete? If yes, an updated_at extract will never see it, and you need either soft deletes in the source, a periodic full key comparison, or change data capture. - Can the source tolerate us? A full table scan of the orders primary at 01:00 is somebody’s outage. Extract from a replica, throttled. - Which time zone is "yesterday"? A logical date in UTC and a business day in New York disagree for five hours, and every report must say which. - Is history rewritten? If a refund ten days later should change the original order’s revenue, the fact table must be revisable that far back - or refunds must be facts of their own, which is usually the better answer. **Functional** - Extract changed rows from 40 source tables and 6 SaaS APIs nightly. - Land raw extracts immutably; stage them typed and deduplicated by key. - Maintain type-2 dimensions (customer, product) and daily-partitioned facts. - Reprocess the last 3 logical dates every night to absorb late data. - Gate publication on data quality checks; publish by atomic partition swap. - Backfill any date range on demand with the same code. **Non-functional** - Ready by 07:00 UTC, 99% of days. - Idempotent: any run, rerun any number of times, produces identical partitions. - Never publish a partition that failed a check. - Source impact capped: at most 50 MB/s read from any replica. - Revenue reconciles to the billing system within 0.01% for every published date. **Out of scope** Real-time dashboards, reverse ETL, the BI tool itself, ML feature stores. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps; a schema of 6 tables and 38 columns. ### Full-text search - the inverted index and the shard that is slow - URL: https://tierslate.com/templates/full-text-search - Kind: system architecture board; level: deep - Access: paid, 800 AI credits - the question is public, the worked answer is not How an inverted index answers a query across shards, and why the slowest shard sets every response time. What is hard about it, left in on purpose: Documents are partitioned by id across 24 shards, which makes indexing local and cheap and makes every query touch all 24 - so query latency is the maximum of 24 shard latencies, and with each shard slow 1 percent of the time, 21 percent of queries see a shard's p99. The defences each cost something real: hedging a second request to a replica after the p95 adds about 5 percent load and does nothing when the slowness is the query itself; fewer, larger shards cut the fan-out but make a shard recovery copy 120 GB instead of 40; partitioning by TERM instead means a query touches only the shards holding its terms, but a two-word query becomes a cross-shard join and the shard holding "the" melts. Relevance has its own quiet error: each shard scores with its own document frequencies, so a rare term looks common on one shard and the merged top 10 is not the true top 10 unless you pay a second round trip. Deep pages are a fan-out multiplier - page 500 asks every shard for its top 5,010. And the index is a derived copy that trails the database by a refresh interval plus the change stream's lag, so a product taken down at noon can still be found at 12:00:02, and anything that needs read-your-write must go to the database instead. #### Start here **Why this design** Product search, log search, help-centre search and code search are all the same machine: an inverted index, split into shards, queried by scatter-gather. Elasticsearch, OpenSearch, Solr and Vespa differ in the details and not in the shape. The interview question "design search for a store with 200 million products" is really three questions - how is a document turned into terms, how does a query find and rank the documents holding them, and what happens when the index is spread over 24 machines - and the third is where most answers stop too early. **What you will learn** - What an inverted index is on disk: a term dictionary, posting lists of doc ids compressed with delta encoding, and why the same analyzer must run at index and query time. - How BM25 scores a match and how WAND skips most of a posting list to find the top 10 without scoring everything. - Why a document-partitioned query waits for every shard, and the exact number: 1 - 0.99^24 = 21 percent of queries meet a slow shard. - What hedged requests buy (p99 from 140 ms to about 45 ms) and what they cost (about 5 percent more load). - Why a document is searchable one second after it is written: immutable segments, refresh, and merge. - The sizing: 200 M documents, 1 TB of index per copy, 24 shards of about 42 GB, 12 nodes, 120,000 shard queries a second at peak. **How to read this board** - Press play: eight steps follow one query out to the shards and back, then one product change from the database into a segment. - ASK: the shopper, the analyzer and the index alias - both parallelograms, because a query passes through them. - SCATTER-GATHER: the coordinator, three of the 24 shards with the slow one in red, the hedge to a replica, and the merge. - INDEX: the database, the change stream, the indexer, the in-memory buffer, segments and the merger. - Pages 1 to 6 in order, then the DATA MODEL: the source rows, the outbox, the index versions, shards, synonyms and the query log that measures the tail. #### 1 · Requirements **Ask these before drawing anything** - How fresh must results be? One second is the cheap answer. "Immediately after I save" means the product page reads the database and search is allowed to lag. - What is a match? Exact words, stems ("running" finds "run"), synonyms ("tv" finds "television"), typos, other languages. Every one is an analyzer decision, and changing one later means reindexing everything. - How is relevance judged? Text score alone, or text plus popularity, price and stock? Business signals are where search quality actually comes from, and they must be in the index to be cheap. - Are there filters and facets? "Under $100, brand Sony, in stock" is a filter that must be fast, and a facet count per brand is an aggregation over every match, not just the top 10. - How deep do people page? Almost never past page 5 - but crawlers and exports do, and they are the ones that hurt. - Is the tail or the average the target? The fan-out makes them very different numbers. **Functional** - Keyword search over title, description and brand with stemming, synonyms and one-edit typo tolerance. - Filters on price, brand, category and stock; facet counts on brand and category. - Ranking by BM25 blended with sales rank and a stock boost. - A product change is searchable within 2 seconds; a takedown disappears within 2 seconds. - Reindex with a new analyzer without downtime. **Non-functional** - 5,000 queries/s at peak, 1,500 average. - p50 under 20 ms and p99 under 60 ms at the coordinator, top 10 results with facets. - 2,000 document updates/s sustained, 20,000/s during a catalogue import. - Losing any node loses no data and no availability; losing a zone degrades latency, not results. - Results are deterministic for a given index state - the same query twice returns the same order. **Out of scope** - Semantic or vector search. It is a second retrieval path with its own index and is blended in at ranking, not a change to this one. - Personalised ranking per user. - Search over data the user is not allowed to see - per-document permissions are a filter problem big enough for its own board. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps; a schema of 6 tables and 41 columns. ## Location & logistics ### Fleet tracking - 21,600 writes per vehicle per day - URL: https://tierslate.com/templates/fleet-tracking - Kind: system architecture board; level: core - Access: paid, 400 AI credits - the question is public, the worked answer is not Teaches how a continuous position firehose is split into a tiny live picture and an enormous history, and how retention rather than hardware decides what it costs. What is hard about it, left in on purpose: History is downsampled from 4 seconds to 30 at ingest, which turns 518 GB a day into 69 GB and is the single reason the storage bill is five figures rather than seven. It is also irreversible and it is applied before anyone has agreed what the history is for. Thirty seconds is fine for "where was this lorry at 14:00" and useless for the questions that pay for telematics: harsh braking lasts under two seconds and is invisible at 30 s, a stop that is 45 s long may be sampled once or twice depending on phase, and an insurance dispute about a collision needs the second before impact, which by then is gone. The board papers over this with a 7-day full-resolution buffer and an event-triggered keep-everything window around detected incidents - which only works if the detector fires, and the detector runs on the downsampled stream for everything older than a week. The retention tiers are also global rather than per customer, so the fleet paying for forensic-grade telematics gets the same 30-second history as the one that only wants a map, and the only lever anybody has is to make it worse for everybody or more expensive for everybody. #### Start here **Why this design** Fleet tracking is the cleanest example of a write-dominated system: half a million devices produce 125,000 writes a second while a few thousand people read the result. Interviewers use it to see whether you do the multiplication before drawing a schema, whether you separate "where is it now" from "where has it been", and whether you treat retention and downsampling as product decisions rather than ops chores. The same shape - IoT telemetry, metrics, ride and delivery tracking - is what real platform teams build and pay for, and the bill is decided by three numbers set long before the first server is bought. **What you will learn** - How one report interval (4 s) becomes 21,600 points per vehicle per day, 10.8 billion writes a day and 125,000/s sustained - and why changing that interval is the biggest lever on the bill. - Why last-known position (75 MB, in memory, overwritten, not durable) and history (append-only, columnar) must be different stores. - How tiered retention - 7 days at 4 s, 90 days at 30 s, 5 years at 5 min - turns 215 TB compressed into about 4.8 TB, and why downsampling is irreversible. - Why ingest must never reject, and how idempotent writes on (vehicle_id, reported_at) make retries and offline back-fill safe. - Why the map viewport query uses geohash prefix ranges over a sorted set rather than S2 or H3. - Why alerts such as harsh braking must be evaluated on the stream, because they do not survive 30-second sampling. **How to read this board** - Diagram, top band, left to right: THE FIREHOSE (vehicle, gateway, accept-always, the ingest stream), then TWO DESTINATIONS, TWO LIFETIMES (last known vs history). Press play for the eight-step walkthrough. - Middle band, WHAT IS KEPT: the downsampler, trip segmenter, stream rules and retention tiers - the decision worth 45x. - Bottom band, WHO IS WATCHING: dispatcher, console, viewport query, live push and replay - 800 reads a second in total. - Then the DATA MODEL: five tables, and the biggest one is not in Postgres. - Page 1 · Requirements - the questions that change every number, and the never-reject ingest target. - Pages 2 and 3 · Capacity and Storage - the one-line multiplication, then the tier-by-tier retention arithmetic. - Page 4 · Availability - the gateway tier as the real single point of failure, and what degrades rather than dies. - Page 5 · How it works - one point from device to map and history, then Page 6 · Deployment, including one-node-at-a-time gateway rollouts. #### 1 · Requirements Half a million vehicles reporting continuously, and a handful of dispatchers looking at a map. The load and the product are on completely different scales, and the design is mostly about keeping them apart. **Ask these before designing** - How often does a vehicle report, and who decides? 4 s, 10 s and 60 s are the same product and a 15x difference in every number on the capacity page. This is the first question and it is usually answered by a firmware default nobody revisits. - What is the history FOR? A map replay needs 30 s. A harsh-braking score needs 1 s. A collision reconstruction needs 10 Hz accelerometer data this board does not even carry. Downsampling is irreversible, so this question must be answered before ingest, not after. - Does the customer see live, or recent? "Live" means a push channel and a write path that cannot lag. "Within a minute" means a poll and a much cheaper system. - How long must data be kept, and by whom is that mandated? Tachograph and hours-of-service rules put a legal floor under retention that no amount of cost engineering may go below. - Is the vehicle ever offline? Long-haul trucks lose signal for hours. Either the device buffers and back-fills - which means out-of-order arrivals of thousands of points - or the history has holes. Both are real designs; only one of them is usually considered. - Per-customer retention, or one global policy? Per-customer is the right answer commercially and the expensive one architecturally, because it makes every partition boundary a per-tenant decision. **Functional** - Ingest positions from 500,000 devices, tolerate duplicates and out-of-order back-fill. - Answer "where is every vehicle in this map viewport" in under 200 ms. - Push live position updates to open consoles. - Replay any vehicle’s track for any day within retention. - Segment the raw stream into trips (ignition on to ignition off) and daily rollups. - Raise alerts - speeding, geofence-adjacent, idling, harsh braking - from the stream rather than from the history. **Non-functional** - Ingest must never reject. A device that cannot deliver retries, and 500,000 devices retrying together is the only real way to take this system down. Accept, acknowledge, and sort it out downstream. - Last-known position fresh within one report interval (4 s). - Viewport query p99 under 200 ms with up to 20,000 vehicles in frame. - Ingest availability 99.99%; console availability 99.9%. They are different numbers because dropping the map is an inconvenience and dropping the stream is a hole in a legal record. - Writes are idempotent on (vehicle_id, reported_at) - the only defence against back-fill that works. **Explicitly out of scope** Geofence evaluation and enter/exit events (that is the geofencing board), route planning and navigation, driver identity and payroll, video telematics, maintenance scheduling. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps; a schema of 5 tables and 36 columns. ### Geofencing - many points against many polygons - URL: https://tierslate.com/templates/geofencing - Kind: system architecture board; level: deep - Access: paid, 800 AI credits - the question is public, the worked answer is not Teaches how a covering index reduces every-point-against-every-fence to a handful of tests, and why a jittery GPS makes the crossing itself the harder problem. What is hard about it, left in on purpose: Hysteresis is what makes the product usable and it is a deliberate, unmeasurable loss of correctness: a 50 m buffer and a 30-second dwell mean a genuine crossing is reported late, and a genuine brief visit - a courier who is inside a 60 m fence for twenty seconds - is never reported at all. The numbers are global rather than per fence, so a 10 km haulage yard and a 40 m drop-off point get the same 50 m buffer, which is negligible for one and larger than the other. The board also evaluates strictly per point rather than per segment, so a device that reports at 09:00:00 outside and 09:00:04 outside, having driven straight through a small fence in between, produces no event whatsoever - the crossing happened entirely between two samples, and nothing here interpolates the path to notice. Raising the report rate to fix that multiplies the largest cost in the system, and segment-based evaluation would fix it properly and doubles the index work, so neither is drawn here. Exactly-once delivery is claimed only up to the customer endpoint being honest about its idempotency key; a customer that ignores the key gets at-least-once and no way to tell. #### Start here **Why this design** Geofencing is two hard problems wearing one name. The first is a multiplication - every reported point against every fence - that no cluster can afford, so the question becomes how to index AREAS rather than points. The second is that GPS lies by 5 to 50 metres, so deciding whether a crossing really happened is harder than detecting one. Interviewers use it to probe spatial indexing and stateful exactly-once event delivery; logistics, ride-hailing and retail-marketing platforms run this exact pipeline. **What you will learn** - Why the naive design is 375 billion point-in-polygon tests a second, and how one S2 cell lookup reduces it to about 212,500. - Why fences are indexed with an S2 region cover (areas, not points), and what geohash and H3 do worse here. - Why hysteresis - a buffer distance, a dwell time and a confirmation count - is the feature, not a refinement: a parked van otherwise emits 900 events an hour. - Why each transition is a compare-and-set on a (device, fence) state row, and why the webhook idempotency key is built from the transition, not the attempt. - Why the whole fence definition (2.7 GB) and index (1.0 GB) fit in memory, while the 384 MB of state decides billing. - The crossing nobody sees: a small fence driven through between two samples. **How to read this board** - Press play once: the walkthrough follows one position from the gateway to a delivered webhook. - THE MOVING POINT, then THE FENCES: the stream in, and the index it is tested against. - DID IT REALLY HAPPEN: hysteresis and the compare-and-set on state. - THE OWNER: who draws the fences and receives the events. - DATA MODEL: the fence, its cell cover, the per-device state and the event log. - Then pages 1 to 6 in order: requirements, capacity, storage, availability, how it works, deployment. #### 1 · Requirements Two million moving points, three million fences, and a customer system that will act on every event we emit. The requirements divide neatly into "make the matching possible" and "make the events trustworthy", and the second is harder. **Ask these before designing** - How many fences, and how big? Three million small fences and three thousand enormous ones are different indexes. A covering that produces 40,000 cells for one national-park-sized fence will quietly dominate the whole index. - Is an enter event worth money? If it triggers billing, a duplicate is a refund and a missed one is a dispute, and the whole board tightens. If it turns a light on, at-least-once is fine and half this design is unnecessary. - Does the crossing itself matter, or only the state? "Tell me when it enters" needs transition detection. "Tell me which devices are inside now" is a query and needs almost none of the hysteresis machinery. - How long may an event be late? Two seconds means evaluating on the hot path of ingest. Two minutes permits a batch, and a batch is an order of magnitude cheaper. - What counts as being inside? Inside the polygon, inside it for thirty seconds, or inside it with the engine off? Every product means the third and every spec says the first. - Who owns the false positives? GPS is noisy by 5-50 m depending on sky. Somebody must choose between late events and duplicated ones, and that person is not the engineer. **Functional** - Define fences as circles or polygons, with an owner, a rule and an endpoint. - Evaluate every incoming position against the fences that could contain it. - Emit ENTER, EXIT and DWELL events with the position and time that caused them. - Suppress events caused by GPS noise rather than by movement. - Deliver each event to the owner’s endpoint with retries and an idempotency key. - Answer "which devices are inside this fence now" without replaying anything. **Non-functional** - Event latency p95 under 3 s from the position that caused it. - No duplicate ENTER for one crossing, enforced by a compare-and-set on the state row rather than by the evaluator being careful. - Evaluation must be O(candidate fences), never O(all fences) - this is the requirement the whole index exists to satisfy and it should be stated as one. - 99.95% for evaluation; delivery 99.9% with retries over 24 hours. - Fence changes live within 60 s, including the re-covering of a redrawn polygon. - Evaluation is idempotent per (device, fence, position timestamp), because the position stream will be replayed. **Explicitly out of scope** Producing the positions (that is the fleet-tracking board), routing and ETA, proximity search over points, indoor positioning and Bluetooth beacons - which solve the same product problem with entirely different physics. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps; a schema of 5 tables and 36 columns. ### Maps routing - precompute, then watch it go stale - URL: https://tierslate.com/templates/maps-routing - Kind: system architecture board; level: deep - Access: paid, 800 AI credits - the question is public, the worked answer is not Teaches how a shortest path over a continental road graph is bought with precomputation, and what live traffic does to work that was done last night. What is hard about it, left in on purpose: Live traffic is applied as a penalty overlay at query time rather than being contracted into the hierarchy, because re-running forty minutes of preprocessing for one blocked road is absurd. The cost is that contraction hierarchies are only CORRECT for the weights they were built with: a shortcut was inserted because it preserved a shortest path under last night's weights, and an overlay that makes one edge ten times slower can make that shortcut a lie. The board handles it by letting the search relax downwards when an overlay edge is on the shortcut path, which turns a 12 ms query into a 60-90 ms one exactly during rush hour, when both the traffic and the query volume peak together. Nothing here measures how often that happens, and the ETA is computed from the same overlay, so a route the search declined to re-examine carries an ETA that is confidently wrong. The rebuild runs every three hours and takes forty minutes, which means the hierarchy is on average ninety minutes out of date with the road network itself - fine for traffic, quietly wrong for a road that closed permanently this morning. #### Start here **Why this design** Maps routing is the cleanest example in system design of buying query time with precomputation - and of paying for it when the world changes. A continental road graph is 60 million nodes, and plain Dijkstra settles most of them, so a 12 ms answer is only possible because most of the work was done hours earlier. Interviewers use it to probe whether you can reason about preprocessing versus freshness, and whether you notice that the expensive tier is probe ingest rather than routing. Google Maps, Apple Maps and every logistics router make this exact trade. **What you will learn** - Why plain Dijkstra fails at continental scale, and how contraction hierarchies - node ordering plus shortcut edges - turn a search into a few hundred settled nodes. - What the trade costs: about 40 minutes of contraction on 64 cores, every 3 hours, so the hierarchy is on average 90 minutes behind the road. - Why live traffic is a query-time overlay rather than a rebuild, and how a large weight change can invalidate the very shortcut that made the query fast. - Why ingest dominates: 500,000 probes/s at peak against 8,100 route requests/s, each probe map-matched to an edge. - Why snapping a GPS point to a road is an S2 level-16 cell lookup over line segments. - Why an ETA is a forecast - historical speed profiles per edge per hour of week for the part of the route not reached yet - not a sum of current speeds. **How to read this board** - Press play once: the walkthrough follows one route request from a GPS point to an ETA. - THE QUERY, then PRECOMPUTED: the request with its graph version pin, and the forty-minute build it depends on. - TWELVE MILLISECONDS: snapping, the bidirectional upward search and the overlay check. - LIVE TRAFFIC: the probe stream that exists to invalidate the band above it. - DATA MODEL: the source of truth for the graph, not the serving path. - Then pages 1 to 6 in order: requirements, capacity, storage, availability, how it works, deployment. #### 1 · Requirements Two hundred million route requests a day over a graph of 130 million edges whose weights change every five minutes. Every requirement below is really a question about how much work may be done before the question is asked. **Ask these before designing** - Is the cost function fixed? Contraction hierarchies are precomputed FOR a cost function. One metric (fastest by car) is one hierarchy. Fastest, shortest, avoid-tolls, lorry-with-a-height-limit and bicycle are five hierarchies, or one much harder algorithm. - Must the route be optimal, or merely good? Optimal permits CH and forbids most heuristics. "Good" permits A* with landmarks and a much simpler operation. Interviewers rarely say which they want, and the answer changes the entire board. - How live is live traffic? Five-minute aggregates are a different system from thirty-second ones. The second forces the overlay design; the first would almost permit folding traffic into a nightly rebuild. - Does the route have to stay stable during the trip? If a reroute may fire whenever a better path appears, the driver is sent round in circles as the traffic oscillates. Stability is a product requirement that constrains the algorithm. - Is the road graph the same for everybody? Turn restrictions, vehicle profiles and access rules make it not quite one graph, and every per-user variation is either a filter at query time or another hierarchy. - What is the acceptable staleness for a road that closed permanently? This is the requirement that decides the rebuild cadence, and it is usually stated as traffic freshness by mistake. **Functional** - Given two points, return an optimal route with a distance, a duration and turn-by-turn geometry. - Snap each endpoint to a real road edge, respecting direction and access rules. - Apply live traffic to the duration and, where it is severe enough, to the route itself. - Serve an ETA that accounts for expected conditions along the route at the time the driver will actually be there. - Ingest GPS probes from navigating devices, map-match them to edges, and aggregate speeds. - Rebuild the hierarchy on a schedule without interrupting queries. **Non-functional** - Route p50 12 ms, p99 90 ms. The p99 is dominated by queries whose shortcut path has been invalidated by the overlay, which is the design’s honest cost rather than a tuning problem. - The answer must be the true optimum under the applied weights, or the product is quietly wrong in a way no user can detect and every competitor comparison exposes. - Traffic visible in the ETA within 90 s of the probes that imply it. - 99.99% for routing, degrading to free-flow weights rather than failing. - The graph is immutable per version. A route computed against version N must be unpackable against version N, which makes version pinning per session a correctness requirement. **Explicitly out of scope** Map data production and conflation, geocoding and address search, indoor and pedestrian routing, ride dispatch (that is the matching board), multi-stop vehicle routing, which is a different NP-hard problem wearing the same coat. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps; a schema of 5 tables and 33 columns. ### Proximity search - the index is the design - URL: https://tierslate.com/templates/proximity-search - Kind: system architecture board; level: starter - Access: paid, 200 AI credits - the question is public, the worked answer is not Teaches how to size a geospatial index against the query it serves, and why the nearest answer is usually in the cell next door. What is hard about it, left in on purpose: The board picks one cell size - geohash length 6, about 1.2 km by 0.6 km - and a fixed nine-cell fan-out, which is the correct trade only for a search radius near 1 km in a city of average density. In Manhattan those nine cells hold 4,000 candidate places and the exact distance filter throws away 97% of what it fetched; in rural Montana they hold two, so the query widens to length 4 and now scans an area 30 km across to find anything at all. The board handles that by switching index, which means there are two indexes to keep consistent and a discontinuity in result quality exactly at the boundary between them. A true answer - S2 or an adaptive quadtree that splits on density, so a cell holds a roughly constant NUMBER of places rather than a constant AREA - is deliberately not drawn here, because it replaces a lookup you can compute in the client with a tree you must traverse on the server. Geohash cells are also not square and get narrower towards the poles, so the nine-cell neighbourhood covers a different real area in Oslo than in Nairobi, and nothing on this board corrects for it. #### Start here **Why this design** Proximity search - "restaurants within 1 km of me" - is the standard entry point to geospatial system design, the core of Yelp, Google Maps and every store locator. Interviewers use it to see whether you know that a database cannot range-scan two dimensions at once, how a grid or geohash turns a radius into a handful of key lookups, and what happens at cell edges and in places where density is wildly uneven. It is also a clean lesson in a read-heavy system: about 1,150 index reads for every index write. **What you will learn** - Why the cell edge should match the query radius, and why geohash length 6 (about 1.2 km x 0.6 km) suits a 1 km search in an average city. - Why the answer is often in the next cell, so a search is always the query cell plus its 8 neighbours - 9 lookups at any density. - Why nine cells are a candidate set, not an answer: 765 candidates on average, 4,050 in a dense cell, then an exact distance filter. - How to handle an empty neighbourhood by widening to a coarser index, and what a second index costs in consistency. - How to size by read amplification - 8,100 index lookups and 688,000 candidate rows a second at peak, against 3.5 place edits a second. **How to read this board** - Press Play and follow the walkthrough: THE QUERY (a point and a radius) and THE INDEX across the top, then THE SEARCH (nine cells, then arithmetic). - Then THE WRITE PATH underneath - 3.5 writes a second against 8,100 lookups. - 1 · Requirements - the radius, the density range and how fresh a result must be. - 2 · Capacity estimation and 3 · Storage estimation - the read amplification, and why the index fits in memory. - 4 · Availability - why the index is derived and can always be rebuilt. - 5 · How it works, then 6 · Deployment plan. - Finally the DATA MODEL below the band, and then the argument this board leaves open on purpose: one fixed cell size is right for Paris and wrong for Manhattan and rural Montana, and the fallback index makes result quality jump at the boundary. #### 1 · Requirements Fifty million places that barely move, and twenty-five million searches a day that all ask the same question from a different point. Everything below is downstream of one decision: how big is a cell. **Ask these before designing** - What is the query radius, and is it fixed? The whole index is sized against it. "Within 1 km" and "the 20 nearest" are different systems: the first is a bounded area, the second is an unbounded expansion that stops when it has enough. - Is "near" the ranking, or a filter before the ranking? If results are ranked by rating and distance is a filter, the index returns a set. If they are ranked by distance, the index must return them in order, and that changes whether a cell scan is enough. - How stale may a place be? A restaurant that closed last month still being returned is a product bug, not an index bug, but it decides whether the index can be rebuilt nightly or must be written synchronously. - Do we need polygons, or only points? "Within 2 km of me" is a point query. "Inside this delivery zone" is a polygon containment test, which geohash cannot do and PostGIS can. Answering the wrong one of these is how a geohash index ends up with a PostGIS instance bolted to its side. - What fraction of searches are in the ten densest cells? If it is most of them, a constant-area grid is the wrong shape and the honest answer is an adaptive tree. - Is the searcher’s own position accurate? A 50 m GPS error and a 1 km radius are fine together. A 50 m error and a "what is in this building" query is not a search problem at all. **Functional** - Given a point and a radius, return the places within it, ranked. - Filter by category, open-now and rating, applied after the geometry rather than before it. - Widen automatically when a cell neighbourhood is too sparse to fill a page of results. - Owners create, move and close places; the index reflects a change within a minute. - Serve the exact-distance number, not just the ordering, because the UI shows "420 m". **Non-functional** - Search p99 under 120 ms server-side, of which the index lookup is under 15 ms and the rest is ranking and hydration. - Recall of the true nearest 20 must be 100% within the stated radius. This is what the 9-cell fan-out buys, and it is a correctness requirement: a proximity search that silently misses the closest result looks perfect in a demo. - Read-dominated by four orders of magnitude - see the capacity page. Design for reads and let writes be slow. - Availability 99.99% for search, degrading to a cached and slightly stale result set rather than an error. - Cell ids are computed on the client as well as the server, so the encoding must be a pure function with no service call in it. **Explicitly out of scope** Routing and travel time (that is the routing board), continuous tracking of the searcher, polygon containment and enter/exit events (that is the geofencing board), personalised ranking and ads. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps; a schema of 5 tables and 34 columns. ### Ride matching - the nearest car is the wrong car - URL: https://tierslate.com/templates/ride-matching - Kind: system architecture board; level: core - Access: free - everything below is the whole board Teaches how a geospatial index turns supply and demand into a dispatch decision, and why that decision is an assignment over a batch rather than a nearest-neighbour lookup. What is hard about it, left in on purpose: Matching runs in a two-second batch so it can solve an assignment over every open request at once instead of greedily handing each rider the nearest car - which is the right call at 08:30 and a pure two-second tax on the rider at 03:00, when there is one request in the cell and the optimal assignment is obvious. The board never adapts the window, because adapting it means the p50 pickup time changes with load in a way nobody can explain to a driver. Worse, the batch does not actually close the race it was partly meant to close: batches are per H3 cell and run independently, so two adjacent cells can both see the same driver sitting on their shared boundary and both assign them. The claim is therefore still a compare-and-set on the driver row and the loser waits for the next batch - two seconds later, having already waited two seconds. And the supply/demand ratio that drives surge is computed per res-8 cell over a 60-second window, which means a stadium emptying reads as extreme scarcity in one cell and normal supply in the six around it, because the grid does not know about the stadium. #### Start here **Why this design** "Design Uber" is one of the most-asked system-design questions because it hides a location system inside a dispatch product. A ride is booked a few hundred times a second; the cars report where they are 150,000 times a second. Interviewers use it to see whether you notice that ratio, whether you can index moving points (H3 hexagons here), and whether you can explain why the nearest car is often the wrong car. Real dispatch teams live with the same three problems: write volume nobody keeps, a matching decision that must be globally sensible, and a claim that must never give one driver to two riders. **What you will learn** - How to size from moving dots, not rides: 600,000 drivers online reporting every 4 s is 150,000 location writes a second - 405x the peak request rate - and almost none of it is kept. - How a geospatial index works for moving points: latitude and longitude become an H3 resolution-8 cell id, and a search is the cell plus its 6-neighbour ring. - Why dispatch runs as a two-second batch that solves an assignment over every open request in a cell, instead of greedily handing each rider the nearest car. - Why the claim is a compare-and-set on one column of the driver row, and why batching per cell still needs it at the cell boundary. - How surge pricing falls out of per-cell supply and demand counters over a 60-second window - 150 writes a second that set the price. **How to read this board** - Press Play and follow the walkthrough: DEMAND (a fare is quoted before any car exists) and THE INDEX across the top. - Then THE DISPATCH DECISION in the middle - a batch, not a lookup - and SUPPLY underneath it: 150,000 writes a second, none of them kept. - 1 · Requirements - pickup time, fairness to drivers, and what "matched" has to guarantee. - 2 · Capacity estimation and 3 · Storage estimation - why this is a write-heavy system and the location stream lives in memory. - 4 · Availability - what degrades (matching falls back to greedy nearest-driver) and what fails closed (the claim). - 5 · How it works, then 6 · Deployment plan. - Finally the DATA MODEL below the band, and then the argument this board leaves open on purpose: a fixed two-second window is right at 08:30 and a pure tax at 03:00, and two adjacent cells can still both claim the same driver. #### 1 · Requirements Two sides of a market that can both walk away, on a map, in real time. The design question underneath every requirement below is which geospatial index the matcher reads. **Ask these before designing** - Is this dispatch or is this a marketplace? If drivers can decline, matching produces offers and the assignment is provisional. If they cannot, it is allocation and the whole board simplifies. The answer changes the data model, not just a policy. - What is the index actually for - radius queries or cell aggregates? "Drivers within 2 km" and "supply divided by demand in this cell" are different questions. The second is what drives pricing, and it is the one that makes hexagons worth the trouble. - How stale may a driver position be at match time? 4 seconds is a smooth map and 150,000 writes/s. 10 seconds is 60,000 writes/s and a matcher that regularly offers a ride to a driver who has already turned off the road it assumed. - May the rider be shown a car before a driver has accepted? If yes, the ETA is a promise made on somebody else’s behalf. If no, the rider stares at a spinner for eight seconds. - Is the pickup ETA road-aware or straight-line? Straight-line is one multiplication. Road-aware is a routing call per candidate driver, which at 370 requests/s and 12 candidates each is 4,400 routing calls/s. - What happens to a rider nobody accepts? Widen the ring, raise the fare, or fail honestly. All three ship; only one of them is usually designed. **Functional** - A rider requests a ride from a pickup point and is quoted a fare and an ETA. - Drivers report position continuously while online and are indexed by H3 cell. - The matcher assigns open requests to available drivers and offers each assignment to its driver. - A driver accepts or declines; a declined or expired offer returns the request to the next batch. - Exactly one driver is ever assigned to one request, and one driver to at most one request. - Surge multiplier per cell, from the supply/demand ratio over a rolling window. - Track the trip from acceptance to completion. **Non-functional** - Request to assignment p95 under 5 s end to end, of which the batch window is 2 s and is spent on purpose. - No double assignment, ever. A driver claim is a compare-and-set, not an UPDATE. This is a correctness requirement and not a performance one. - Cell aggregates fresh within 60 s. Surge computed on a stale ratio is a price charged for a shortage that has already ended. - Matching availability 99.95%, degrading to greedy nearest-driver rather than failing. - Location writes are lossy by design: a dropped ping costs 4 seconds of freshness for one driver and nothing else. - Demand is not a curve but two spikes and a stadium: 08:00-09:30, 17:30-19:00, and an unpredictable third that is somebody’s concert ending. **Explicitly out of scope** Payments and driver payouts, identity and background checks, in-trip navigation (that is the routing board), ratings and disputes, pooled/shared rides - which changes matching from an assignment to a much harder routing problem. #### 2 · Capacity estimation From 12M rider DAU, 1.5M registered drivers and 8M completed rides/day. As in every board in this category, the ride rate is not the load - the moving dots are. **Requests** - 8,000,000 / 86,400 = 92.6 requests/s average - Morning and evening peaks carry 35% of the day in 3 hours: 2,800,000 / 10,800 s = 259/s - Friday 23:00 city peak, measured rather than derived: 370 requests/s - Each request is retried into roughly 1.35 batches (declines and expiries), so the matcher sees 500 request-slots/s at peak **Driver location - the arithmetic that decides the architecture** - A device reporting every 4 seconds is 86,400 / 4 = 21,600 writes per device per day - A driver is online about 8 h, so 7,200 writes/driver/day - 1,500,000 drivers x 7,200 = 10.8 billion location writes/day - At peak 600,000 drivers online: 600,000 / 4 = 150,000 writes/s - That is 405x the peak request rate. The system is a location system that happens to dispatch cars. **What the matcher reads** - A batch runs every 2 s per active cell. At peak roughly 9,000 cells are active in the fleet - Each batch reads its own cell and its 6-neighbour ring = 7 cell reads, plus one ring-2 expansion for sparse cells (18 more cells) in about 20% of batches - 9,000 cells / 2 s x (7 + 0.2 x 18) = 47,700 cell reads/s - Each read is one Redis sorted-set range: the index is the only thing standing between this and 150,000 rows scanned per batch **Surge aggregates** - Supply and demand counters per cell, 60-second tumbling window: 9,000 cells x 1 write/60 s = 150 writes/s. Trivial, and it is the number that sets the price. **Read : write ratio** - Writes: 10,800,000,000 pings + 8,000,000 rides x ~14 state and offer rows = 10.9 billion/day - Reads: 47,700 cell reads/s x 86,400 = 4,120,000,000, plus rider polling 8,000,000 x 180 = 1,440,000,000 = 5.6 billion/day - Read : write = 1 : 1.9 - write-heavy, and every bit of that is one number that changes four times a minute per car. #### 3 · Storage estimation **The location stream, which is 99% of the bytes and 0% of the durable data** - One ping: vehicle id 16 B + lat/lon 16 B + heading 2 B + speed 2 B + accuracy 2 B + timestamp 8 B + overhead ~12 B = 58 B - 10,800,000,000 pings/day x 58 B = 626 GB/day if every point were written - Live position is held, not stored. One key per online driver, overwritten: 600,000 x 120 B (position, H3 cell, state, vehicle) = 72 MB total, and it fits in a single Redis node with room left over. - The durable track is downsampled to one point per 30 s before anything touches a disk: 626 GB / 7.5 = 83 GB/day, kept 90 days = 7.5 TB, for disputes and pickup-quality analysis only. **The transactional data** - ride_requests: 1 x 420 B (with the pickup/dropoff cells and the quoted fare) - match_offers: 1.35 x 160 B = 216 B - trips: 0.82 x 560 B = 459 B (18% of requests never become a trip) - driver state transitions: 6 x 90 B = 540 B - Total 1,635 B per request x 8,000,000 = 13.1 GB/day - Indexes x1.7 = 22.3 GB/day = 8.1 TB/year, five years = 40.7 TB - Requests older than 18 months go to columnar object storage at ~8x, so the live cluster stays near 12 TB **The index itself** - The H3 index is derived, not stored durably: cell id is a function of lat/lon, so the sorted sets are rebuilt from the next ping within one ping interval - 9,000 active cells x ~70 drivers x 30 B = 19 MB. The entire geospatial index of a national fleet is smaller than one photograph. - surge_cells: 9,000 rows, rewritten every 60 s, with a 24 h history for pricing audits: 9,000 x 1,440 x 60 B = 778 MB/day, kept 30 days = 23 GB **Five-year total** About 41 TB of rides and offers, 7.5 TB of rolling downsampled tracks, 23 GB x 12 of surge history, and 72 MB of live state. The part of this system that dispatch actually reasons about would fit on a phone. #### 4 · Availability **Targets** - Ride request accepted: 99.95% - Matching: 99.95%, degrading to greedy nearest-driver rather than failing - Driver claim (the compare-and-set): 99.99% and failing closed - a claim that is not certain must be refused, because the failure mode is two riders in one car - Live map: 99.9%, degrading to a static pickup pin and a text ETA - Measured weighted by request volume, so an outage at 18:00 counts roughly 3x one at 04:00 **Single points of failure, named** - The matcher for a city. It is stateful for two seconds at a time and deliberately single-writer per cell group: two matchers running the same cells would assign the same driver twice, which is worse than a two-second gap. Leader election per city, restart from open requests in about 8 s, and open requests simply wait one extra batch. - The driver location cache. In-memory, unreplicated for writes, and not durable on purpose. Losing it loses at most 4 seconds of freshness per driver; the index refills from the next ping. - The requests primary for a city shard. The claim is a compare-and-set, which needs one writer per driver row. Primary plus synchronous replica; failover RTO 30-40 s, during which new requests in that city are refused rather than matched optimistically. - The H3 library version. A cell id is a pure function of a library, so an upgrade that changes a boundary re-partitions the index. Pinned, and rolled out by rebuilding the index rather than by deploying. **Replication and failover** - Requests, offers and trips: Postgres sharded by city, primary plus synchronous replica in a second AZ. The matcher never reads an async replica - a stale read here assigns a driver who is already on a trip. - Location: Redis Cluster, sharded by H3 cell prefix so a cell and its neighbours land on few nodes, no persistence, replica for failover only. - Trip events: 3x replicated, 7-day retention, partitioned by trip id. **What degrades rather than dies** - Assignment solver slow or down: fall back to greedy nearest-available-driver per request. Measurably worse pickup times at peak, no correctness loss, and it is the same code path the board describes as the wrong answer - kept precisely because it is a safe wrong answer. - Ring-2 expansion too slow: stop expanding and fail sparse cells to "no cars available" rather than holding a rider for eleven seconds to find one 6 km away. - Surge pipeline down: freeze the multiplier at its last value and stop it from rising. A frozen price is a business decision; a price computed from a stale ratio is a refund. - Routing service down: pickup ETA falls back to straight-line distance x 1.4. Less accurate, and the matcher’s ranking changes slightly, which is acceptable for minutes. - Offer push down: drivers poll every 3 s instead. Battery cost, no lost rides. #### 5 · How it works Every online driver’s phone sends a position every four seconds. The location gateway does one thing with it that matters: it computes the H3 cell at resolution 8 - a hexagon about 460 m across - and writes the driver into that cell’s sorted set, removing them from the previous one. Nothing here is persisted. A position that is worthless in four seconds does not deserve a disk, and the index is a pure function of the position, so it rebuilds itself continuously. Why hexagons and not a geohash. A geohash is easier: it is a string, prefixes nest, and "nearby" is a prefix scan. But its cells are rectangles that get narrower as you go north, its eight neighbours sit at two different distances, and its boundaries fall wherever the binary interleave happens to put them. For a radius query none of that matters much. For this system it does, because the matcher’s primary question is not "who is within 2 km" but "what is the ratio of supply to demand in this cell compared with the six around it" - and that is a comparison between cells, which needs cells that are comparable. H3 hexagons have six neighbours all the same distance away and near-equal area within a region. S2 would be the other defensible answer, with better global area uniformity and a proper cell hierarchy; it loses on the ring being eight cells rather than six. PostGIS with a GiST index is the right answer for a system with a hundredth of this write rate, and the wrong one at 150,000 writes a second. A rider requests a ride. The request service quotes a fare - base plus distance plus the current multiplier for the pickup cell - and writes the request as OPEN with its pickup cell. It does not look for a driver. Instead, every two seconds a matcher wakes up per active cell and takes every open request in that cell together. It reads the cell and its six neighbours, and expands to a second ring only if the first one is thin. Then it scores each (request, driver) pair: pickup ETA, the driver’s direction of travel, how long the rider has waited, whether the driver is about to finish a trip nearby, and how scarce supply is in the cell the driver would be leaving. It solves the assignment over the whole batch. This is the part worth arguing about. The reflex is to hand each rider the nearest car. Nearest by straight line is not nearest by road - a river is 200 m wide and 4 km around. Nearest now is not nearest in ninety seconds. And taking the only car in a cell to serve a rider 900 m away leaves the next request in that cell with nothing. Greedy dispatch is a local optimum that gets worse exactly when the system is busiest. A batch of two seconds is enough to turn a lookup into an assignment, and it costs every rider two seconds including the one at 03:00 who was the only request in the city. The assignment is then claimed, one driver at a time: UPDATE drivers SET state = 'OFFERED' WHERE id = $1 AND state = 'AVAILABLE'. Batches run per cell and independently, so two adjacent batches can both see the same driver standing on their shared boundary, and exactly one of them may win. The loser’s request goes back into the next batch. Without that compare-and-set, one car is dispatched to two people and both of them are watching it on a map. The winning driver gets an offer with a twelve-second expiry. Accept and the trip begins; decline or let it lapse and the request re-enters the next batch, with its waiting time now weighing more heavily in the score. Meanwhile counters per cell feed the supply/demand ratio that sets the multiplier, on a sixty-second window - which is why a stadium emptying registers as a shortage in one hexagon and nothing at all in the six around it. #### 6 · Deployment plan **Regions** - Sharded by city. A city is a closed marketplace: its riders, drivers and cells never interact with another city’s, which makes the shard key, the deploy unit and the blast radius the same thing. - Three regions hosting city shards by geography; a city never spans regions. - The location cache is regional and never cross-replicated. A driver in Lisbon is of no interest in Toronto. **What is stateful, and how it moves** - ride_requests / match_offers / trips (Postgres): expand-contract only. Offers and trips are append-mostly; the one hot mutable column is the driver state the claim writes. - The matcher: stateful for the length of one batch. A deploy drains it - finish the current batch, stop taking new ones, restart, resume from OPEN requests. Disruption is one batch, 2 seconds, per cell group. - The location cache (Redis): never migrated. Stand up an empty cluster, point the gateway at it, and it is complete within one ping interval - 4 seconds. The only stateful component here that genuinely needs no migration plan. - The H3 index: derived, so it is not migrated either. A resolution change (8 to 9, say) is a config flag that is rolled forward by letting the new cells fill and the old ones expire; both are written for one ping interval during the flip. - Surge windows: in-memory counters, rebuilt from the event stream in 60 s. A deploy loses at most one window, which freezes the multiplier rather than resetting it. **Rollout** - Deploy by city, in local off-peak - 13:00 to 15:00 local. Because cities are independent this is a real blast-radius limit, not a percentage spread evenly over everybody. - Three smallest cities in a region first, soak 2 hours. - Gates: double assignments = 0 (this one is absolute), p95 request-to-assignment within 400 ms of baseline, offer acceptance rate within 2 points, unmatched-request rate within 1 point, surge multiplier distribution unchanged. - Then 10 cities, then the region, then the next region. Three days for a full rollout, and that is the intended speed. - Hard freeze Friday and Saturday evenings in any timezone the release touches, and around any event the demand forecaster has flagged. **Rollback** - Application: previous image per city in under 3 minutes. - Matching policy - batch window, ring size, score weights, offer TTL - is runtime configuration. Retuning it is the first response to a supply incident and must never need a deploy. The greedy fallback is reachable by flipping one flag. - Schema: expand-contract with tested down scripts. - Not rollback-able: a trip that happened, a fare that was quoted at a multiplier the new code would not have produced, and a driver who declined an offer they should never have been sent. All three are handled forward - refund, adjustment, an apology in the driver app - and none of them is fixed by redeploying yesterday’s binary. #### Walkthrough 1. **Six hundred thousand dots, every four seconds** - A device reporting every 4 seconds is 86,400 / 4 = 21,600 writes a day on its own. Six hundred thousand drivers online at peak is 150,000 writes a second, four hundred times the request rate. This is a location system that happens to dispatch cars, and recognising that is the first design decision. 2. **Lat and lon become a cell id** - The gateway turns the position into an H3 resolution-8 cell - a hexagon about 460 m across - and moves the driver between sorted sets. Hexagons rather than a geohash because the matcher compares supply to demand BETWEEN cells, which needs cells that are comparable: six neighbours all at the same distance and near-equal area. S2 is the other defensible answer; PostGIS is the right answer at a hundredth of this write rate and the wrong one here. 3. **A price from a hexagon** - The rider is quoted before anybody looks for a car: base plus distance times the multiplier current in the pickup cell. The multiplier is supply over demand in that one hexagon over the last 60 seconds, which is why a stadium emptying reads as a shortage in one cell and business as usual in the six around it. 4. **OPEN, and it waits on purpose** - The request is written OPEN and nothing goes looking for a driver. There is no queue on this board: the set of OPEN requests in a cell IS the queue, and the matcher drains it on a timer rather than on arrival. 5. **The nearest car is the wrong car** - Every two seconds the matcher takes all open requests in a cell together, reads the cell and its six neighbours, and solves the assignment over the whole batch. Nearest by straight line is not nearest by road - a river is 200 m wide and 4 km around. Nearest now is not nearest in ninety seconds. And giving the only car in a cell to a rider 900 m away leaves the next request with nothing. 6. **Two riders, one car** - The assignment is claimed driver by driver: UPDATE drivers SET state = 'OFFERED' WHERE id = $1 AND state = 'AVAILABLE'. Batches run per cell and independently, so two adjacent batches can both see the same driver on their shared boundary and exactly one may win. Without that compare-and-set, one car is dispatched to two people who are both watching it move. 7. **Twelve seconds to say yes** - The winner gets an offer that expires in twelve seconds. Accept and the trip row is written; decline or lapse and the request returns to the next batch with its accumulated wait weighing more in the score. Only 82% of requests ever become a trip, and that gap is the honest measure of whether any of this works. 8. **The two seconds everybody pays** - Here is the honest weakness. The batch buys a globally better assignment at peak and charges every rider two seconds for it, including the one at 03:00 who was the only request in the city - and the board never adapts the window, because a pickup time that changes with load is one nobody can explain to a driver. The batch does not even close the boundary race it helps with, which is why the compare-and-set is still load-bearing. #### Schema - drivers (Live position is NOT here. It is a key in memory, overwritten every 4 s. What is here is the one column two matchers race on.): id uuid PK, city_id uuid, state varchar, vehicle_class varchar, home_cell varchar, last_ping_at timestamptz, rating decimal - ride_requests (A request is OPEN until a batch assigns it. It may pass through several batches, and the wait is an input to its own score.): id uuid PK, rider_id uuid, pickup_cell varchar, pickup_lat double, pickup_lon double, quoted_minor bigint, state varchar, batches_seen smallint, created_at timestamptz - match_offers (An offer is a provisional assignment with an expiry. It exists because a driver may decline, and it is the row a dispute is answered from.): id uuid PK, request_id uuid, driver_id uuid, batch_at timestamptz, score decimal, pickup_eta_s smallint, state varchar, expires_at timestamptz - trips (Only 82% of requests get here. The gap between requests and trips is the honest measure of whether matching works.): id uuid PK, request_id uuid, driver_id uuid, accepted_at timestamptz, pickup_at timestamptz, dropoff_at timestamptz, distance_m integer, fare_minor bigint - surge_cells (One row per H3 res-8 cell that has seen activity. The price of a ride is a property of a hexagon, which is either elegant or absurd depending on where the hexagon boundary falls.): h3_cell varchar PK, city_id uuid, open_requests integer, available_drivers integer, multiplier decimal, updated_at timestamptz ### Parking reservation - a spot is a time slot with a location - URL: https://tierslate.com/templates/parking-reservation - Kind: system architecture board; level: core - Access: paid, 400 AI credits - the question is public, the worked answer is not How spots are booked by time window without double booking, and what happens when a car overstays. What is hard about it, left in on purpose: The exclusion constraint makes double booking impossible in the ledger, and does nothing about the car that will not leave. The overstay reserve - 6% of each garage never sold - absorbs ordinary lateness, but overstays are correlated: when a concert runs forty minutes late, a third of the venue garage overstays at once, the reserve is gone in minutes, and drivers with valid 22:30 bookings meet a full gate. The board can see it coming (occupancy minus expected exits is below incoming bookings) and can only re-seat into reserve bays that no longer exist, or refund. The gate is the truth about HOW MANY cars are inside but not WHICH: about 5% of plate reads fail first time, so a misread car has no reservation to be late against, and the overstay detector either misses it or, after a manual correction, bills the wrong plate. Nothing here reconciles the occupancy count with the set of reservations marked ARRIVED, so the two drift through the day and are reset only when the garage empties at night. #### Start here **Why this design** Parking reservation is the booking problem with arbitrary time ranges instead of nights, and with an inventory that physically refuses to free itself on time. Interviewers use it to see whether you can prevent overlapping bookings without a race (and know why "check for overlaps, then insert" is one), how you turn a pool of interchangeable spaces into something a constraint can protect, and what you do when the car does not leave. **What you will learn** - Why a booking is a range, not exploded slots, and how an exclusion constraint makes overlaps impossible rather than unlikely. - Why a garage is split into virtual bays, so an at-most-one constraint can protect a 400-space pool. - Why a turnover buffer is part of the range the constraint compares. - Why 6% of every garage is never sold, and what that reserve is for. - Why the gate count beats the reservation table about how many cars are inside. - Why overstays are correlated, which is the failure the reserve cannot absorb. **How to read this board** - Press play once: the walkthrough follows one car from search to the minute it does not leave. - BOOK: search, availability, the bay assignment and the constraint. - ARRIVE: the gate, plate reads and live occupancy. - OVERSTAY: detection, re-seating the next driver, billing. - DATA MODEL: bays, reservations as ranges, and gate events. - Then pages 1 to 6 in order: requirements, capacity, storage, availability, how it works, deployment. #### 1 · Requirements A driver wants a guaranteed space near a place for a window of time. A garage operator wants every space earning. The system sits between a promise and a car park. **Ask these before designing** - Is a booking for a specific spot or any spot in a garage? Garages are usually pooled (any space on the level); driveways and marked bays are specific. This board models both as bays. - What granularity? 15 minutes is standard. Arbitrary ranges at that granularity are why the board keeps ranges rather than slots. - Is there a gap between bookings? A turnover buffer of 10-15 minutes, so the next driver does not arrive to find the last one reversing out. - How is arrival detected? Plate recognition (ANPR) at the gate, a QR code, or an app check-in. ANPR is frictionless and wrong about 5% of the time on the first read. - What happens on overstay? Grace period, then per-15-minute charge - and what happens to the driver booked next? **Functional** - Search garages near a point for a time window, with price and walking distance. - Book a window; get a guaranteed space or a clear refusal. - Enter and exit by plate recognition, with a QR fallback. - Extend a booking from the app if the bay is free afterwards. - Detect overstays, notify, and charge per started 15 minutes past the grace period. - Re-seat or refund the next driver when an overstay blocks them. **Non-functional** - No double booking in the ledger, ever - enforced by the database. - Booking p99 under 500 ms, including during event on-sales at 2,000 bookings/minute for one venue. - Gate open p95 under 1.5 s from the car stopping at the barrier. A queue at a barrier is a traffic problem on a public road. - Gate works offline. It must admit valid bookings with the backend unreachable. - Overstay detection within 2 minutes of end plus grace. **Out of scope** Dynamic pricing, EV charging sessions, monthly permits, enforcement on street parking, operator payouts. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps; a schema of 5 tables and 35 columns. ### Package tracking - scan events and the promised date - URL: https://tierslate.com/templates/package-tracking - Kind: system architecture board; level: core - Access: paid, 400 AI credits - the question is public, the worked answer is not How carrier scan events become one shipment timeline, and why events arrive late and out of order. What is hard about it, left in on purpose: Status is projected with a fixed precedence rank so a late "out for delivery" can never overwrite "delivered" - but that same rank cannot express a real regression, so a failed delivery attempt, a parcel returned to a hub or a carrier correcting a mistaken "delivered" either freezes the page on the wrong status or needs a hand-written exception per carrier. The poller covering carriers with no webhooks wants about 11,000 requests a second to refresh 20 million parcels every 30 minutes, far above the per-account quotas carriers allow, so those parcels are staler than the rest and nothing on the page says so. And the ETA model learns from lane history, so on the busiest week of the year - when hubs are backed up - it promises the average week. #### Start here **Why this design** Every online shop, marketplace and tracking aggregator has to turn scans from carriers it does not control into one honest page: where is my parcel and when will it arrive. It looks like a status column, and it is really an event-ordering problem. Scans come from dozens of carriers in different formats, arrive minutes to hours after they happened, are sometimes sent twice, and occasionally contradict each other. Interviewers use it to see whether you separate event time from arrival time, make ingestion idempotent, and know the difference between a status (derived) and a scan (a fact). **What you will learn** - Why ingest acknowledges in 50 ms and queues, and why the queue is partitioned by tracking number. - Normalising 1,200 carrier event codes into 14 statuses, and the timezone bug hidden in "2026-11-27 14:05" with no offset. - The dedup key that makes a resent webhook and a duplicate EDI line the same event. - Projecting status by sorting on scanned_at and applying a precedence rank, so a late scan lands in the past. - An ETA model per lane, and notifying the customer only when the predicted day changes. - Why polling carriers without webhooks runs into their rate limits, and adaptive poll cadence. **How to read this board** - Press Play. CARRIERS on the left, INGEST in the middle, TIMELINE on the right, then TELLING PEOPLE along the bottom. - 1 · Requirements - what "accurate" can mean when you do not own the scanner. - 2 · Capacity and 3 · Storage - events, active parcels and the polling bill. - 4 · Availability - what happens when a carrier goes quiet for six hours. - 5 · How it works, 6 · Deployment plan, then the DATA MODEL. #### 1 · Requirements **Ask these before drawing anything** - How many carriers, and which of them push? The large ones offer webhooks; many regional and last-mile carriers offer only polling APIs or batch files. - Whose promised date is shown - the carrier's, the merchant's checkout promise, or our own prediction? They disagree, and showing two dates is worse than showing one. - Is the page allowed to be minutes stale? It almost always is; the scans themselves are. - Who gets notified, on what changes, and by which channel? - Does a merchant need the same events by webhook to trigger their own flows (refunds on lost parcels, review requests on delivery)? **Functional** - Register a shipment by carrier and tracking number at label creation. - Accept scan events by webhook, poll, or batch file, normalise them, and show one timeline ordered by when each scan happened. - Show a current status and a predicted delivery date, updated on every new scan. - Notify the customer on out for delivery, delivered, exception, and when the predicted delivery day moves. **Non-functional** - A scan received by any route is on the page within 60 seconds of arrival. - Duplicate or re-sent events never produce a duplicate timeline entry or a duplicate notification. - A late or out-of-order scan never moves the status backwards past a terminal state. - Tracking page p99 under 150 ms; it is the most refreshed page the merchant has. - ETA within the predicted day for 90 percent of parcels on an ordinary week. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps; a schema of 6 tables and 42 columns. ### Surge pricing - a price set by a map that changes every minute - URL: https://tierslate.com/templates/surge-pricing - Kind: system architecture board; level: deep - Access: paid, 600 AI credits - the question is public, the worked answer is not How supply and demand per map cell set a multiplier, and why the price must be locked when it is shown. What is hard about it, left in on purpose: The multiplier is a feedback controller with a 60-second period acting on a city that reacts in two to five minutes, so without heavy smoothing it overshoots and oscillates - 2.5x, then 1.0x as drivers flood in, then 2.5x again as they leave - and the smoothing that stops that is also why it reacts late to a concert emptying 40,000 people in eight minutes. Cell boundaries are arbitrary: a rider can cross the road into a cell at 1.3x from one at 2.4x, and the spatial smoothing narrows that cliff without removing it. The locked quote protects the rider and moves the risk to the platform: a quote at 1.2x accepted after the cell has gone to 2.5x is a ride priced below what it takes to attract a driver, and it may simply not be matched. And supply is self-reported by the people paid by the multiplier - drivers at an airport going offline together to trigger surge is a documented pattern, and nothing on this board yet tells a real shortage from a coordinated one. #### Start here **Why this design** Surge pricing looks like a formula - demand over supply, times a constant - and the formula is the easy ten percent. The design question is about time and space: how big a piece of the city one price covers, how often it changes, how it is kept from oscillating when drivers and riders react to it, and how a price that changes every minute can still be a promise to the one rider who saw it. Every marketplace with dynamic pricing (rides, delivery, hotels, tickets) has the same skeleton. **What you will learn** - Why the city is cut into H3 hexagons, and what resolution 7 versus 8 versus 9 does to noise and fairness. - Why the multiplier is recomputed on a 60-second clock from windowed counts rather than per request. - Surge as a feedback controller: lag, overshoot, and the spatial and temporal smoothing that tame them. - Why a shown price is a signed quote with an expiry, and what honouring it costs the platform. - Guardrails that are not engineering at all: caps, emergency freezes and fairness rules, and why they belong in the pipeline rather than in a policy document. **How to read this board** - Press Play. SIGNALS: drivers ping their location, riders open the app and ask for prices. - PER CELL, PER MINUTE: the aggregator counts both per hexagon, the surge computer turns ratios into multipliers, the guardrails smooth and cap them. - THE MAP is published, versioned, and pushed to drivers as a heatmap. - THE LOCKED PRICE, along the bottom right to left: a quote reads the map and locks the price, and the ride request honours it. - Then pages 1 to 6 and the DATA MODEL. Argue with the oscillation, the cell boundary and the quote the platform regrets. #### 1 · Requirements **Ask these before drawing anything** - What is surge FOR? Clearing the market (fewer riders, more drivers) is different from maximising revenue. This board optimises for completed trips and short pickup times; revenue is a consequence. - How small is a "place"? Too large and a stadium is averaged into its quiet neighbourhood. Too small and a cell has two drivers and its ratio is noise. - How fast must it react? A concert ending is an eight-minute event. A rainstorm is a two-hour one. A 60-second recompute covers both if the windows are chosen well. - Is the price shown the price paid? For upfront pricing, yes - which forces a quote with an expiry. - Are there legal caps? Many cities and states cap multipliers during declared emergencies. That is a hard requirement with a regulator behind it. - Do drivers see the map? Showing it is how surge recruits supply; it is also how supply learns to game it. **Functional** - Maintain a multiplier per active H3 cell, recomputed every 60 seconds, between 1.0x and a per-market cap (for example 3.0x), in steps of 0.1. - Quote: given origin, destination and product, return a fare including the multiplier, valid for 5 minutes, with an id. - Request: accept a ride only with a valid, unexpired, unused quote, and charge exactly its fare (adjusted only for route changes the rider caused). - Push a surge heatmap to drivers in the market. - An operator can freeze or cap a market, or a polygon, instantly. **Non-functional** - Quote p99 under 100 ms: it is on the screen the rider is looking at. - A published map is visible to every quote server within 5 seconds. - Every multiplier ever published, and every quote, is reproducible for at least a year - regulators and disputes ask "why was I charged 2.4x at 23:12?" - The pricing pipeline failing must fail to 1.0x, never to the last high value. **Out of scope** - Base fare computation (distance, time, tolls). - Driver matching itself - see the ride-matching board. - Driver pay, which is decoupled from rider price in upfront pricing. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps; a schema of 5 tables and 37 columns. ## Collaboration ### Calendar scheduling - a time that suits five people - URL: https://tierslate.com/templates/calendar-scheduling - Kind: system architecture board; level: core - Access: paid, 400 AI credits - the question is public, the worked answer is not Why a meeting time is computed from rules and guesses rather than read from a table, and what that costs. What is hard about it, left in on purpose: Every free/busy answer here is a cached, rounded, deliberately vague reply from a system this one does not control - opaque blocks with no titles, half-hour granularity, a short horizon, a fifteen-minute TTL and a sizeable share of lookups that simply fail. The board then presents the intersection as though it were a fact, and there is no reservation anywhere: two organisers scheduling at the same moment will both be offered the same slot and both will take it, with the collision surfacing later as an RSVP. Treating an unknown as free is what makes that happen and treating it as busy makes the product useless, so the board treats it as free and says so here. Underneath, tzdata is versioned state baked into every binary: when a government moves a DST boundary, every materialised UTC instant is wrong until a re-expansion job has run, and any service still holding the old rules disagrees with the ones that have upgraded - which shows up as reminders an hour early for a fraction of users and as nothing at all in any error rate. #### Start here **Why this design** Scheduling looks like a set-intersection exercise and is really a lesson in data that is not a fact: recurring events are rules rather than rows, local times are not instants, and another company’s free/busy is a deliberately vague cached guess. Interviewers use it to see whether you model time correctly before you scale anything; calendar teams live with the consequences, because every bug here is a meeting at the wrong hour rather than an error in a log. **What you will learn** - Why a recurring series is stored as ONE row holding an RRULE, how occurrences are expanded per window, and how exceptions are keyed by recurrence id. - When the local wall-clock time plus an IANA zone is the truth and when the UTC instant is, and what to do with the 02:30 that does not exist on DST morning. - How a "find a time" request fans out into per-attendee expansion, a five-minute busy bitmap and a working-hours mask - and where its 1.2 s budget goes. - Why cross-organisation free/busy is opaque, rounded and cached, and why rendering "unknown" as free is the bug users actually report. - Why the system proposes a slot but reserves nothing, and how reminders fire exactly once through an idempotency key. - Treating tzdata as a versioned release artefact with its own rollout and re-expansion job. **How to read this board** - Diagram, left to right: PEOPLE, then THE ASK (the request), then RECURRENCE (one row, meetings computed), then THE ANSWER (merge into a proposal). - Then the right-hand column: TIME ZONES, a property of every value, and ANOTHER ORGANISATION, the system this one cannot interrogate. Press play for the walkthrough. - Page 1 · Requirements - the questions to ask before drawing, and the latency and correctness targets. - Pages 2 and 3 · Capacity and Storage - the numbers that make expansion, not storage, the hot path. - Page 4 · Availability - what degrades rather than dies, and the failures with no error. - Page 5 · How it works - the whole request told end to end, then Page 6 · Deployment, including the tzdata rollout. - Finally the DATA MODEL at the bottom: five tables, and the one with no foreign key that most answers come from. #### 1 · Requirements **Ask these before drawing anything** - Is a recurring event one thing or many? One row with a rule is the only affordable answer, and it means every read does work and every exception is a patch. - Does "09:00 every weekday" mean the same wall clock or the same instant? Wall clock, always, for a recurring meeting - which means its UTC time moves twice a year and the stored instant is a cache, not the truth. - What happens to a recurring meeting at 02:30 on the morning the clocks go forward, when 02:30 does not exist? Pick a rule, write it down, and apply it in exactly one place. - Can an external organisation see whether we are busy, and at what granularity? The answer is a policy decision with an architecture attached - opaque blocks, coarse rounding, and a short horizon are all deliberate. - Is a proposed time held while the organiser decides? Almost no calendar system does this, so two organisers can and will take the same slot. Decide whether you are fixing that or accepting it. - What does an unanswerable free/busy lookup mean - free or busy? Free books over people. Busy makes the product useless. There is no correct answer, only a chosen one that has to be visible. - How far ahead may anyone schedule? It bounds recurrence expansion, which is the hottest operation in the system. - Do we have to interoperate over iCalendar and iTIP? If yes, a large share of the semantics is decided by a specification from 1998 rather than by this design. **Functional** - Create a one-off or recurring event, with attendees, in a named time zone. - Edit or cancel a single occurrence of a series, or this-and-all-following, without disturbing the rest. - Find candidate times for N people over a window, respecting each person’s working hours in their own zone. - Read free/busy for people in other organisations, at whatever granularity they will give. - Track per-attendee responses, and show the organiser who has not replied. - Fire reminders at the right local time, exactly once, per attendee preference. **Non-functional** - Render a month of a calendar with 180 recurring series: p95 under 400 ms. - Suggest times for 5 attendees over 14 days: p95 under 1.2 s, including external lookups, with a hard 800 ms budget on each external call and an explicit "unknown" when it is exceeded. - A reminder fires once. Not twice, which is worse than not at all, and not an hour out. - Instance identity is stable: an occurrence keeps its identity across edits to the series, because attendee responses and reminders are attached to it. - 99.95% for reading a calendar. Free/busy for external domains has no availability target at all, because it is not this system. **Out of scope** - Room and resource booking, which needs actual reservation semantics and is therefore a different board. - Video conferencing links, which are an attachment. - Travel-time and location awareness. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 9 steps; a schema of 5 tables and 42 columns. ### Code review - the comment is on a line that moved - URL: https://tierslate.com/templates/code-review - Kind: system architecture board; level: core - Access: paid, 400 AI credits - the question is public, the worked answer is not What it takes to keep a conversation attached to code that is rewritten underneath it while the conversation is still going. What is hard about it, left in on purpose: Comments are re-anchored by mapping the old head to the new one through an interdiff, with a context hash as the fallback and an "outdated" marker when both fail - and roughly 9% of comments go outdated on a typical force-push, at which point they are collapsed, unread and effectively deleted by the interface rather than by anybody deciding to delete them. Approvals have the same anchoring problem and the board deliberately does NOT solve it the same way: an approval is stored against a head_oid, and dismissing it on every push is correct and is turned off by most teams within a month because it is annoying, which leaves an approval attesting to code nobody approved. Then the merge gate: requiring a branch to be up to date before merging is the only way to catch two changes that pass separately and fail together, and it serialises the entire repository - so the board adds a speculative merge queue, whose batches are held in memory by one process, and a deploy or a crash in the middle of a batch throws away every build in it and re-runs the lot. #### Start here **Why this design** Code review looks like a workflow - propose, discuss, approve, merge - and the workflow is an afternoon. The real problem is that the code under discussion is replaced while the discussion is still going: every force-push moves the lines that comments and approvals were attached to, and every merge invalidates the green build of every other open change. Interviewers use it to see whether you notice that a comment is anchored to bytes in a commit rather than to a line number, and whether you can reason about a merge gate that is correct without serialising a whole repository. Teams that build review tools spend most of their effort on exactly these two things. **What you will learn** - How a comment is anchored - blob hash, side, line and a context hash - and why the file path is deliberately not its identity. - How an interdiff (old head against new head) re-places most comments by pure arithmetic, when the bounded context-hash search takes over, and why roughly 9% are marked outdated rather than guessed. - Why an approval should be stored against a head_oid, and what quietly breaks when teams stop dismissing approvals on push. - Why the merge gate reads stored check verdicts and never calls CI live. - Why "up to date before merge" serialises a busy repository into 114 hours of work a day, and how a speculative merge queue batches six changes to fit it into 19. - Why the storage bill is CI logs at a petabyte a year rather than review rows at eleven terabytes, and how retention keeps it at 19 TB. **How to read this board** - Diagram, left to right: PEOPLE, then THE PROPOSAL (a push becomes two hashes), then CI · LOUD, AND NOT ON THE COMMENT PATH. - Then the wide middle band, ANCHORS · WHERE DID LINE 42 GO - the actual subject of the board - and the right-hand column: THE GATE · WHAT MUST BE TRUE and AFTER · WHO IS NOW BLOCKED. Press play for the nine-step walkthrough. - Page 1 · Requirements - the decisions to force before drawing: rewriting, outdated comments, stale approvals, up-to-date merges. - Pages 2 and 3 · Capacity and Storage - why re-anchoring is two cores, why the merge queue is the real bottleneck, and why logs are the storage bill. - Page 4 · Availability - the merge queue as the only live state, and the misplaced comment as the failure with no alert. - Page 5 · How it works - one force-push told end to end, then Page 6 · Deployment plan, including draining the queue and shadowing resolver changes. - Finally the DATA MODEL at the bottom: five tables, where the anchor is a third of the comment row and all of the work. #### 1 · Requirements **Ask these before drawing anything** - May the branch be rewritten, or only appended to? Append-only makes anchoring almost trivial and makes the history unreadable. Every team picks rewriting and then meets this board. - What should happen to a comment on a line that no longer exists? There is no correct answer. Pick between "silently move it somewhere plausible" and "say it is outdated", and know that the second one means nobody reads it again. - Does an approval survive a new push? The honest answer is no. The popular answer is yes. Decide deliberately, because the difference is whether an approval is evidence or decoration. - Must a branch be up to date with the target before merging? Yes catches the two-changes-that-pass-separately failure and serialises the repository. No is faster and lets that failure through. This is the single biggest performance decision here. - How many required checks, and who may make a check required? A required check with a flaky runner is a repository-wide outage with no incident channel. - Are comments threaded, and can a thread be resolved by anyone or only by its author? This changes what "unresolved" means in the merge gate, which is the one place it has teeth. - How long are CI logs kept? This is the entire storage bill of the product and it is usually decided by nobody. - Is review required for every change, including reverts during an incident? The break-glass path should be designed, logged and loud - not discovered at 3am. **Functional** - Propose a change as a branch against a target, with a description and a diff. - Comment on a specific line of a specific file, in a thread, and resolve it. - Survive a branch update: comments follow their code where it can be found, and are marked outdated where it cannot. - Approve or request changes, recorded against the exact head that was reviewed. - Run required checks per head commit and report their state. - Merge only when the gate is satisfied, with a recorded reason when it is bypassed. **Non-functional** - Diff render for a 400-file change: p95 under 1.5 s, cached thereafter. - Re-anchoring after a push: p95 under 3 s for 50 comments, and it must be idempotent - running it twice on the same pair of heads produces the same anchors. - A comment, once posted, is never lost. Being marked outdated is not loss; being dropped because the re-anchor threw is. - The merge gate is evaluated from stored facts only, never from a live CI query. A gate that depends on an external service being up is an outage waiting for a deploy freeze. - 99.9% for reading and commenting. 99.5% for merging: a blocked merge is annoying, a wrong merge is not recoverable by retrying. **Out of scope** - The version control system itself - the branch update is this board’s input. - Running the builds. This board queues them and reads their verdicts. - Static analysis and suggestion bots, which arrive here as ordinary comments and have the same anchoring problem in a louder voice. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 9 steps; a schema of 5 tables and 42 columns. ### Collaborative editor - two carets in one paragraph - URL: https://tierslate.com/templates/collaborative-editor - Kind: system architecture board; level: deep - Access: paid, 600 AI credits - the question is public, the worked answer is not How two people type into one document at the same time without either of them losing a character. What is hard about it, left in on purpose: An open document is one mutable object in one process, mutated synchronously and written to the database 250 ms later, debounced - which is what makes a keystroke appear in 40 ms and what makes every deploy a data-loss risk. The board answers with a SIGTERM flush that writes every dirty room before the process exits and refuses to exit zero if any of them failed, and that answer is only as good as the grace period: a SIGKILL, an OOM or a node that simply vanishes takes the last 250 ms of every room on it. Nor does the flush help the document that is TOO BIG to save, which is refused at write time and carries on being edited in memory against a database that will never hold it. And the room is pinned to one process by construction, so there is no second replica to fail over to - a rolling deploy is a planned disconnection of every editor in the fleet, timed rather than avoided. #### Start here **Why this design** A collaborative editor is the cleanest test of whether you understand concurrent state: every client has already applied its own edit before the server sees it, so the system is about reconciling divergence rather than preventing it. Interviewers use it to probe the OT-versus-CRDT decision, ordering, and what "acknowledged" means when the truth lives in one process’s memory. Real teams (Google Docs, Figma, Notion) live with the operational consequence - a room pinned to a process makes every deploy a planned disconnection. **What you will learn** - Why local echo forces optimistic replication, and how an operation carries a base revision so the server can rebase it onto the head. - How operational transformation depends on one process seeing every op in one total order - and why that pins each document to a single room. - What a CRDT costs instead: per-character ids and tombstones that turn a 12 KB document into roughly 320 KB over its life. - Why presence is a separate, never-persisted pipe that carries about twice as many frames as the edits themselves. - How op log retention decides whether an offline client’s edits can be merged at all - a storage choice that is really a correctness choice. - How a debounced, revision-guarded flush and a SIGTERM drain bound data loss to the last 250 ms, and what a SIGKILL still costs. **How to read this board** - Diagram, top row left to right: PEOPLE, then THE CLIENT · APPLIES FIRST (local echo and the pending-ops queue), then THE ROOM · ONE WRITER, NOTHING AWAITED (edge socket, document server, transform, room doc in RAM). - Then the middle band, FAN-OUT, PRESENCE AND THE EDIT THAT ARRIVED LATE: the broadcast that doubles as the ack, the caret channel, and the reconnect-and-rebase path for Editor B. - Then the right-hand column: DURABILITY · OFF THE HOT PATH ON PURPOSE and COMPACTION · A LOG THAT NEVER ENDS. Press play for the nine-step walkthrough. - Page 1 · Requirements - the questions that decide OT or CRDT before anything is drawn, and the 16 ms local-echo target. - Pages 2 and 3 · Capacity and Storage - 288k ops/s and a million presence frames/s on eighteen machines; a 1.5 PB op log that snapshots cut to 24 TB, and the CRDT priced alongside. - Page 4 · Availability - the room process as the structural single point of failure, and what degrades rather than dies. - Page 5 · How it works - one character end to end, then the offline edit; Page 6 · Deployment - drain, flush, jittered reconnect. - Finally the DATA MODEL at the bottom: five tables, and why the op log dwarfs the documents it describes. #### 1 · Requirements **Ask these before drawing anything** - Must an edit appear instantly on the machine that made it? If yes - and it is always yes - then every client is already divergent and the whole board is about reconciling, not about preventing. - How many people may edit one document at once? Two is a different system from two hundred, and two hundred is mostly a presence problem rather than an editing one. - Is the document plain text, rich text, or a tree with attributes? OT on a flat string is a well-understood ninety lines; OT on a tree with formatting spans is where implementations go to die. - Must edits made offline be accepted, or may they be refused? "Accepted" means keeping enough history to transform them forward, which sets the op log retention, which sets the storage bill. - Does the product need per-character attribution - who typed this word? That is a different data structure from "who changed this document", and asking for it later is a rewrite. - How long is a document expected to live? A CRDT accumulates metadata per character that survives deletion, so a five-year document is a different size from a five-day one. - Is there a server at all? Peer-to-peer or local-first removes the ordering authority and forces a CRDT. Saying "we have a server" is the decision that makes OT available. - What is the largest document anyone may open? There is always a limit and it is always found by a user rather than written down. **Functional** - Two or more people edit one document simultaneously and every one of them converges on the same text. - An edit appears on the machine that made it before any network round trip. - Each participant sees the others’ carets and selections, labelled and coloured stably. - An edit made while disconnected is not lost: on reconnect it is transformed onto the current head and applied. - A document can be reverted to any earlier point for as long as the op log retains it. - A view-only participant sees live changes and cannot send any. **Non-functional** - Local echo: under 16 ms, which is one frame. This is not negotiable and it is why local-first is a requirement, not a design. - Remote application: p50 under 120 ms, p99 under 400 ms, same region. - Convergence: after any sequence of concurrent edits and any delivery order, all replicas that have seen the same set of ops hold the same document. This is a correctness property, not a latency one. - 99.95% availability for opening and editing. Presence has no availability target at all. - Durability: an acknowledged edit survives a clean shutdown. An acknowledged edit does NOT currently survive a hard kill of the process holding the room, and that gap is stated rather than hidden. **Out of scope** - Comments and suggestions, which anchor to ranges that move and are their own board. - Offline-first mobile with multi-day divergence. - End-to-end encryption, which removes the server’s ability to transform and therefore forces a CRDT. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 9 steps; a schema of 5 tables and 35 columns. ### Version control - the commit graph is a DAG - URL: https://tierslate.com/templates/version-control - Kind: system architecture board; level: deep - Access: paid, 800 AI credits - the question is public, the worked answer is not Why history is a graph rather than a list, and what that costs at merge, at fetch and at garbage collection. What is hard about it, left in on purpose: Content addressing buys free deduplication, cheap verification and an audit trail nobody can forge, and it pays for all of it with an object store that cannot forget. A force-push does not delete a commit, it moves a ref; the objects stay, reachable by hash, until a garbage collection that is deliberately conservative - it will not prune anything younger than two weeks, because pruning an object a concurrent push is halfway through referencing corrupts the repository. So a committed credential is live on the server for at least a fortnight after the mistake, is already in every clone taken since, and the only real remedy is to rotate the secret. The board also leaves the ref update as a bare compare-and-swap: it makes the last push win safely, and it gives the loser no merge and no queue, only a rejection and a human. And garbage collection is a full reachability walk of a graph with no incremental answer, so the housekeeping cost of a repository is a function of its whole history rather than of its recent activity. #### Start here **Why this design** Version control is the cleanest real example of content addressing and of a DAG used as a storage model, and every engineer uses one daily without seeing either. Interviewers ask for it - usually as "design a Git host" - to probe whether you can separate immutable data that replicates freely from the few mutable bytes that need consensus, and whether you see a fetch as a set difference over a graph rather than "everything since a timestamp". Hosting teams live with the consequences: CI clone storms, housekeeping that costs the whole history, and secrets that cannot be deleted. **What you will learn** - Why naming an object by the hash of its bytes gives deduplication (61% across forks), local verification and immutability for free. - Why a commit naming its parents makes history a DAG with no revision numbers, so "came before" becomes a reachability walk. - How a push lands: quarantine, a reachability check, then one compare-and-swap on one ref - the entire concurrency story. - How have/want negotiation finds a common base in two or three round trips, and why a cold clone costs 1,100 times a warm fetch - 134 TB against 122 GB of CI egress a day. - Why a three-way merge needs the merge base, why a conflict is a fact about the base, and what a criss-cross merge does. - Why 240 TB of objects replicate asynchronously while 7.8 GB of refs go through consensus, and why prune keeps a two-week floor. **How to read this board** - The diagram, left to right: PEOPLE, THE WORKING COPY (working tree and index), then OBJECTS · CONTENT IS THE ADDRESS (object writer, object store, refs). - Below them, THE WIRE · WHAT ARE YOU MISSING: packfile builder, transport, the have/want negotiation, the ref log and the server repository. - The right-hand column: MERGE · THREE FILES IN THE ROOM, NOT TWO, then HOUSEKEEPING. Press play for the nine-step walkthrough - one commit, one push, one fetch, one merge, then garbage collection. - Page 1 · Requirements - the questions that decide the shape, and the integrity and latency targets. - Pages 2 and 3 · Capacity and Storage - reads driven by CI, the cold-to-warm ratio, loose versus packed growth. - Page 4 · Availability - why the ref store is the one hard single point of failure. - Page 5 · How it works, told end to end, then Page 6 · Deployment plan - refs in a home region, objects everywhere. - The DATA MODEL at the bottom: five tables, of which git_refs is the only mutable one. - Finish on the deliberate weakness: a force-push deletes nothing, so a leaked credential stays on the server for two weeks and in every clone for good. Discuss rotating versus rewriting, and what the loser of a ref CAS should get beyond a rejection. #### 1 · Requirements **Ask these before drawing anything** - Is history immutable, or may it be rewritten? "Immutable objects, mutable refs" answers both at once and is the decision the rest of the board depends on. - Must two people be able to work offline and merge later? If yes, history cannot be a numbered sequence, because nobody can allocate the next number. - How large may a single file be? Content addressing stores a whole new object for a one-byte change, so a 2 GB binary edited weekly is 100 GB a year of objects that delta-compress against nothing. - How large may the whole history be? Clone cost is a function of history, not of the working tree, and this is what makes a twenty-year-old repository painful rather than the number of files in it. - Do we need per-line attribution? That is a walk back through the graph per line, not a stored fact, and its cost is what makes blame slow on a busy file. - Is a monorepo in scope? Partial and shallow clones stop being optimisations and become requirements somewhere around 10 GB of history. - What happens when somebody commits a credential? Decide this before it happens, because the honest answer - rotate the secret - is unpopular enough that people go looking for a delete button instead. - Who may force-push, and to what? The compare-and-swap protects the repository from corruption; it does not protect a branch from a person. **Functional** - Record a snapshot of a working tree as an immutable commit naming its parents. - Create, move and delete branches - which are names for one hash and nothing else. - Fetch from a remote by exchanging what each side has, sending only the difference. - Merge two branches using their common ancestor, reporting conflicts rather than guessing. - Walk history: ancestry, reachability, and per-line attribution. - Recover a branch that was moved by mistake, for as long as the ref log retains it. **Non-functional** - Object integrity: every object is verifiable against its own name. Corruption is detectable locally with no remote and no checksum file. - A push either fully lands or does not land at all. A partially published set of objects is a broken repository, not a slow one. - Ref updates are linearisable per ref. Two pushes racing on one branch: exactly one wins and the other is told clearly. - Clone of a 1 GB repository: under 4 minutes at 50 Mbps. Fetch of a day’s work: under 2 seconds. - 99.95% for fetch and clone. Push may be briefly refused under load; refusing a push is safe, accepting half of one is not. **Out of scope** - Code review, which is a different board - the branch update here is its input. - Large-file storage as a separate service, which exists precisely because content addressing is the wrong shape for a 2 GB binary. - Signing and trust policy, which sits on top of the hashes rather than inside them. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 9 steps; a schema of 5 tables and 37 columns. ### Whiteboard sync - the cursors cost more than the drawing - URL: https://tierslate.com/templates/whiteboard-sync - Kind: system architecture board; level: core - Access: paid, 400 AI credits - the question is public, the worked answer is not Why a shared canvas is mostly a broadcast problem, and what happens to a shape that two people grab at once. What is hard about it, left in on purpose: A drag emits twenty-five absolute-position ops a second, and last-writer-wins resolves each of them by arrival order - so two people dragging one shape produce a tug of war at 25 Hz that ends wherever the network left it, not where either person meant. The board softens that with a drag claim broadcast on the PRESENCE channel, which is exactly the channel that is unacknowledged, unordered and allowed to drop frames: two people who grab the same shape inside one 60 ms presence window both believe they hold it, and the claim is advisory anyway because a server that enforced it would have to keep per-shape lock state and decide what happens when the holder’s laptop closes. The honest fix is intent-carrying ops - "move by this delta", or a claim on the ordered channel with a server-side lease - and both cost a round trip on the one interaction that has to feel like direct manipulation. Underneath all of it the room is one process holding the board in memory with a 250 ms debounce to storage, so a deploy disconnects everyone and an unclean kill loses the last quarter second of every board on the machine. #### Start here **Why this design** A shared whiteboard looks like a collaborative editing problem and is mostly a broadcast problem: cursors and viewports outweigh the actual edits twelve to one. Interviewers use it to see whether you size the right pipe, whether you know what a single-writer room buys and what it costs at deploy time, and whether you can say precisely what happens when two people drag one shape. The same questions arrive in any product that grows live presence - multiplayer cursors in a document editor, a design tool, a game lobby. **What you will learn** - Why 60 pointer events a second are coalesced over a 40 ms window into 25 ops, and why the receiver, painting at 60 fps, cannot tell. - How presence (16.3M frames/s) outweighs ops (1.3M/s) twelve to one, grows with the square of the room, and is cut from 782 MB/s to 174 MB/s by viewport culling and rate adaptation. - Why one process per board with one revision counter is the cheapest total order, and what it costs: a 250 ms unsaved window, a drain on every deploy, and a 22 MB rejoin at p99. - Why last-writer-wins on absolute positions turns two simultaneous drags into a 25 Hz tug of war, and what the two real fixes - delta ops or a leased claim - each cost. - Why keeping only the first and last op of each drag shrinks the op log by 94%, from 395 GB to 24 GB a day. - Which channel may drop data under load (presence) and which never may (ops), and how each one degrades. **How to read this board** - The diagram, left to right: PEOPLE, then THE CLIENT (60 fps locally, 25 ops on the wire), then THE ROOM (one writer, one rev counter). - Below them, TWO FAN-OUTS: ops on the left, presence on the right - drawn the same size although presence is twelve times bigger - and the red Last writer wins box where the conflict happens. - The right-hand column: DURABILITY, then DERIVED. Press play for the nine-step walkthrough, which follows one drag and then the collision. - Page 1 · Requirements - the questions that change the design, and the latency targets. - Pages 2 and 3 · Capacity and Storage - where the twelve-to-one ratio and the 94% op-log collapse come from. - Page 4 · Availability - which channel may drop, and the failure that has no alert. - Page 5 · How it works, told end to end, then Page 6 · Deployment plan - the drain and the rejoin wave. - The DATA MODEL at the bottom: five tables, where board_shapes.last_writer is how the tug of war gets measured. - Finish on the deliberate weakness: an advisory drag claim sent on the one channel that promises nothing. Argue whether delta ops or a server-side lease is worth a round trip in front of the first pixel of a drag. #### 1 · Requirements **Ask these before drawing anything** - How many people may be on one board? Six is a design; forty is a different design; four hundred is a broadcast product with a canvas attached, and presence has to become one aggregated frame rather than n of them. - Is a drag an intent or a position? "Move to (x, y)" is trivial to implement and loses an argument between two draggers. "Move by (dx, dy)" keeps both intents and drifts. There is no third option that is both. - May a shape be locked while somebody is manipulating it? A real lock needs a lease, a holder, and a rule for what happens when the holder’s laptop closes mid-drag. An advisory claim needs none of that and does not actually prevent anything. - Does the board need to be correct while nobody is looking at it? If yes, the in-memory room is a cache with a write-behind and needs a durability story. If no - and for a whiteboard it largely is no - the debounce can be generous. - How large may one board get? Canvases grow without bound because nothing is ever deleted, only moved off to the side. The p99 board is two orders of magnitude bigger than the median. - Must a late joiner see the board instantly? Sending the whole document is simple and is megabytes; sending a viewport-shaped subset is fast and means the client no longer holds a complete document, which breaks undo. - Is there an audit requirement - who moved this? That turns the op log from an optimisation into a retained record with its own lifetime. - What is the acceptable behaviour on reconnect after two minutes? Replay, or reload? Replay needs op retention; reload loses local unsent work. **Functional** - Several people draw, move and edit shapes on one canvas and converge on the same board. - Every participant sees the others’ cursors, and their viewport rectangles when they are following somebody. - A drag is smooth locally at 60 fps regardless of network conditions. - A late joiner receives the current board and then follows along from that revision. - Undo is per person: undoing your own last action must not undo somebody else’s. - A board survives everybody leaving and is identical when reopened. **Non-functional** - Local feedback: within one frame, 16 ms. A canvas that waits for a server to move a shape is unusable in a way a text editor is not - the pointer is already somewhere else. - Remote application: p50 under 150 ms, p99 under 500 ms in region. Above about 250 ms a remote cursor stops looking alive and starts looking broken. - Presence has no delivery guarantee whatsoever. Dropping frames under load is correct behaviour, not degradation. - 99.9% for opening and editing a board. Presence has no availability target. - Convergence is a correctness property: given the same ops, every client renders the same board. Position disagreement between two clients is a bug at any latency. **Out of scope** - Real-time audio and video, which share the room concept and nothing else. - Vector export and printing, which read a snapshot rather than the live room. - Infinite-canvas spatial indexing for search, which is a separate board. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 9 steps; a schema of 5 tables and 38 columns. ### Kanban board - ordering cards without renumbering them - URL: https://tierslate.com/templates/kanban-board - Kind: system architecture board; level: core - Access: paid, 400 AI credits - the question is public, the worked answer is not How cards are reordered with fractional ranks instead of renumbering, and how live updates reach every viewer. What is hard about it, left in on purpose: Every write to a board takes the same row lock to get its next sequence number, so a board is a single-writer queue - comfortable for twenty people dragging cards, and the ceiling for an automation that bulk-moves 5,000 cards at a few hundred a second while every human on the board waits behind it. A hot insertion point (new cards always added to the top) grows the rank key by roughly one character every six inserts, until the rebalance job rewrites the whole column and broadcasts 2,000 moves to every open tab at once. And a client that was offline replays its moves against neighbours that have since moved, so its card lands in a place that nobody chose. #### Start here **Why this design** A kanban board is a list that many people reorder at the same time while watching each other do it. That makes two questions that every task tool - Trello, Jira, Linear, Asana, GitHub Projects - has had to answer: how do you store the order of cards so that moving one does not rewrite the others, and how do twenty open tabs stay in agreement without a refresh. Interviewers like it because it looks like CRUD and is really an ordering problem plus a fan-out problem, and the naive answer to each (an integer position, broadcast everything) fails in a way you can calculate. **What you will learn** - Why an integer position column turns one drag into a renumbering of the whole column, and how a fractional rank string makes a move one row. - How "key between a and b" works in base 62, why keys grow about one character per six inserts into the same gap, and when to rebalance. - Why the rank column must use a byte-order ("C") collation, or "a0V" and "a0v" sort differently in production than in tests. - A per-board sequence number written in the same transaction as the change, so clients can detect and repair a missed event. - Optimistic moves on the client, rebased when the server's version of events arrives. - WebSocket fan-out through a pub/sub channel per board, and what one move costs on a board 500 people have open. **How to read this board** - Press Play. CLIENT on the left, the WRITE in the middle, then the FAN-OUT along the bottom. - 1 · Requirements and 2 · Capacity - why moves are cheap and fan-out is not. - 3 · Storage - cards, ranks and the event log. - 4 · Availability - reconnects, missed events and a gateway node dying with 40,000 sockets. - 5 · How it works, 6 · Deployment plan, then the DATA MODEL. #### 1 · Requirements **Ask these before drawing anything** - How large does a board get? A team board has 200 cards; an imported backlog has 20,000. Ordering and rendering choices change at the second number. - How many people have one board open at once? Usually 1 to 20; an all-hands planning board can be 500. - Must order be the same for everyone at every instant, or is "converges within a second" acceptable? It is always the second. - Is offline editing in scope? It changes conflict handling from "last writer wins with a fresh view" to genuine merging. - Are there automations or integrations that move cards in bulk? **Functional** - Create, edit, move cards within and between columns; reorder columns. - A move is shown immediately to the person making it and to every other viewer within 1 second. - Two people moving cards at the same time both see the same final order. - A client that disconnects and reconnects catches up without reloading the board. **Non-functional** - Move latency as felt by the mover: 0 ms (optimistic). Server acknowledgement p99 under 150 ms. - Fan-out p99 under 500 ms from commit to every connected viewer. - A move writes O(1) rows regardless of column length. - No lost moves: every committed change is recoverable by sequence number for 30 days. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps; a schema of 5 tables and 28 columns. ### File sync - every laptop is a replica - URL: https://tierslate.com/templates/file-sync - Kind: system architecture board; level: deep - Access: paid, 600 AI credits - the question is public, the worked answer is not How files sync across devices by chunk and version, and what a conflict looks like when two laptops were offline. What is hard about it, left in on purpose: Conflicts are detected per FILE, not per block or per line: two laptops that both edited revision 41 offline both commit, the second is refused by compare-and-set, and the best this board can do is write "report (Laptop B conflicted copy).docx" beside the original and leave a person to merge two binaries by eye. The journal that makes that detection possible is a single ordered sequence per namespace, so one shared team folder with 400 members and a build server writing 3,000 files a minute is one hot row of contention that no amount of sharding can split. And the client is trusted with three trees of state in a local database - if a crash or a disk-full corrupts it, the safe recovery is a full rehash of every file on the laptop, which for 300,000 files is hours of CPU and a support ticket. #### Start here **Why this design** "Design Dropbox" sounds like an upload form with a folder tree. It is actually a replicated database whose replicas are laptops that go offline for a week, whose writers are applications that know nothing about sync, and whose conflicts cannot be merged automatically because the data is a .psd. The question tests whether you separate bytes from metadata, whether you can make a 2 GB file with one changed paragraph cost one block of upload, and whether you can say exactly what happens when two offline edits meet. **What you will learn** - Why files are cut into 4 MiB content-addressed blocks, and how that one decision gives you delta upload, cross-user dedup and resumable transfers for free. - Why the metadata journal and the block store are different systems with different scale, consistency and cost - and why a commit happens only after its blocks exist. - Compare-and-set on a parent revision: the single rule that detects every conflict, and why it can only detect, never merge. - Cursors and long polls: how 50 million idle clients learn something changed without polling a database. - The client as a stateful replica with three trees - local, remote and synced - and why that local database is the most dangerous component on the board. **How to read this board** - Press Play. LAPTOP A saves a file, the chunker finds the three changed blocks, and the commit goes to METADATA. - The missing blocks go to BLOCKS, which only stores, then the commit is appended to the journal. - LAPTOP B is woken by a long poll, reads the journal from its cursor and finds that it edited the same revision while offline. - Then read pages 1 to 6, and the DATA MODEL at the bottom. - Argue with the weaknesses on purpose: file-level conflicts, one ordered journal per shared folder, and a client database you cannot afford to lose. #### 1 · Requirements **Ask these before drawing anything** - Sync only, or also share and collaborate? Shared folders mean a namespace with many writers, which is where every hard problem on this board lives. - How large can a file be? 50 GB video files decide whether resumable, block-level upload is a requirement or a nicety. Here it is a requirement. - How long may a device be offline? If the answer is "indefinitely", the server must keep enough history to bring a 6-month-old replica up to date, or declare it too stale and force a rescan. - What does a conflict produce? Silent last-writer-wins loses work. Conflicted copies keep both and annoy people. Pick one out loud. - Is version history a product feature? 30 days of restorable versions means deleted blocks cannot be deleted for 30 days. - Selective sync and placeholders (files that exist in the tree but not on disk)? It changes the client a lot and the server barely at all. **Functional** - A change on any device appears on every other online device of every member within seconds. - Upload and download transfer only blocks the other side does not already have. - Offline edits are kept and synced on reconnect; concurrent edits never silently overwrite each other. - Restore any file to any version from the last 30 days, including deleted files. - Shared folders: a namespace mounted into several users' trees with per-member permissions. **Non-functional** - Durability of committed data: 11 nines on the block store. A sync product that loses a file is finished as a product. - Change propagation p95 under 5 seconds from commit to the other device starting its download. - Metadata commit p99 under 200 ms; it is the only synchronous step the user waits for. - Clients are hostile environments: crashes, full disks, clock skew of hours, antivirus locking files. The protocol must not trust device clocks for ordering. **Out of scope** - Real-time co-editing inside a document - that is the collaborative-editor board. - Full-text search of file contents. - Mobile camera upload, which is a one-way ingest and much simpler. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps; a schema of 6 tables and 35 columns. ### Multiplayer game server - the server decides what happened - URL: https://tierslate.com/templates/multiplayer-game - Kind: system architecture board; level: deep - Access: paid, 800 AI credits - the question is public, the worked answer is not Why an authoritative server with client prediction hides latency, and how a tick rate sets cost and fairness. What is hard about it, left in on purpose: Lag compensation rewinds hitboxes to where the shooter saw them, up to 200 ms back, so a player with a 15 ms connection who reaches cover can still die "behind the wall" to a player at 150 ms - the design is fair to the shooter by being unfair to the target, and the 200 ms cap is just where the board chose to stop. The tick rate sets both feel and cost: 64 Hz runs about 150 matches on a 32-core host, 128 Hz about 75, so moving to 128 doubles the server bill for a difference most players cannot measure but every competitive player complains about. Each match lives in the memory of exactly one process with no replica, so a crash or a host failure ends the match outright - it is voided and nobody loses rating, which players will learn to exploit by crashing it. And the snapshot still carries enemies the player cannot see, culled only coarsely by distance and rooms; whatever the server sends, a wallhack can draw. #### Start here **Why this design** A real-time multiplayer game is the purest latency problem in system design: the thing being served is a shared present that cannot exist, because every player receives it late. This question is asked for game studios and increasingly for anything real-time (trading screens, collaborative 3D, live sports betting). It tests whether you know the difference between an authoritative server and a relay, whether you can explain prediction, reconciliation, interpolation and lag compensation without hand-waving, and whether you can put a cost on a tick rate. **What you will learn** - Why the server owns the simulation and clients send only inputs - and why peer-to-peer and lockstep lose for a competitive shooter. - The fixed tick: 64 Hz is a 15.625 ms budget, and what has to fit inside it. - Client-side prediction and server reconciliation: your character moves now, and is corrected later if the server disagrees. - Entity interpolation: why other players are drawn about 50 ms in the past on purpose. - Lag compensation: rewinding hitboxes for a shot, who it is fair to, and why it is capped at 200 ms. - Matchmaking, server allocation from a fleet, and why a match is a single process with no failover. **How to read this board** - Press Play. GETTING INTO A MATCH: a ticket, a matchmaker, and an allocator that picks a server for ten players. - THE SERVER DECIDES: inputs arrive along the top, the tick runs, lag compensation rewinds for a shot. - The snapshot goes back to the PLAYERS, where the client reconciles itself and interpolates everyone else. - Then pages 1 to 6 and the DATA MODEL. - Argue with what the board leaves in: the death behind the wall, the cost of 128 Hz, and a match that dies with its process. #### 1 · Requirements **Ask these before drawing anything** - What kind of game? A 5v5 tactical shooter, a 100-player battle royale and a turn-based card game have nothing in common past the login screen. This board is a 5v5 shooter where hits are instant (hitscan). - How competitive? Ranked play makes fairness and anti-cheat requirements; casual play makes cost the requirement. - What latency is playable? Under 60 ms round trip feels good, 60 to 100 ms is fine, over 150 ms is a different game. That decides how many regions you need. - Is the simulation deterministic? Lockstep (send inputs only, everyone simulates) needs bit-exact determinism across CPUs and waits for the slowest player. A shooter almost never chooses it. - What happens when a server dies mid-match? Void, rematch or resume? Resume is very expensive; most games void. **Functional** - Matchmake ten players by skill and latency; place them on a server all ten reach in under 60 ms where possible. - Run the match authoritatively at a fixed tick; clients send inputs, receive snapshots. - Own-character movement responds instantly on the client; other characters move smoothly. - Hits are judged against what the shooter saw, within a compensation window. - Record results, rating changes and a kill feed; record the match for replays and cheat review. **Non-functional** - Tick budget 15.625 ms at 64 Hz, with simulation finishing in under 5 ms so a slow tick never cascades. - Downstream bandwidth under 128 kbit/s per player, so the game works on a poor connection. - Matchmaking median wait under 60 s at peak, under 3 min for the top 1 % of skill. - Match crash rate under 0.05 %. A voided ranked match is a support ticket. **Out of scope** - Accounts, store and inventory. - Voice chat (a separate relay service). - Client-side anti-cheat drivers - mentioned only where server design affects them. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps; a schema of 6 tables and 40 columns. ## Database schemas ### E-commerce orders - URL: https://tierslate.com/templates/ecommerce-schema - Kind: database schema board; level: core - Access: paid, 400 AI credits - the question is public, the worked answer is not Orders, line items and the price that must not change after checkout. What is hard about it, left in on purpose: inventory is a single quantity_on_hand with no reservation, so two concurrent checkouts both pass the stock check and oversell the last unit #### Start here **Why this design** An order schema is the most common database-design question there is, and it is mostly a test of whether you can tell history from state. A price, a product name and an address are facts about today; an order is a record of what was agreed on one particular day, and the schema has to keep those apart or last quarter’s revenue changes every time marketing runs a sale. Interviewers use it to see whether you reach for constraints and transactions before you reach for indexes; real teams meet it as a refund that pays back a number the customer never paid, or as the last unit of a flash-sale item sold twice. **What you will learn** - Why order_lines copies unit_price_minor, sku_at_purchase and name_at_purchase at checkout instead of joining to product_variants for them, and why that makes the variant FK ON DELETE SET NULL. - Why money is a bigint of minor units with a currency beside it, never a float, and why JPY and KWD break the "divide by 100" assumption. - Why a cart is its own table rather than an order with status = 'cart': the NOT NULLs an order needs are exactly the columns a guest cart cannot fill. - How each of the eleven foreign keys picks CASCADE, RESTRICT or SET NULL, and what policy each choice encodes. - Why a single quantity_on_hand oversells under concurrency, the one-statement conditional decrement that stops stock going negative, and the reservation model that the board deliberately leaves out. - How to change this schema on a live database: expand/contract, NOT VALID constraints, batched backfills, and partitioning orders by month when the composite key it forces ripples into every child table. **How to read this board** - Schema, top to bottom: CATALOGUE (products, product_variants, inventory), then CUSTOMER (customers, addresses), then CART (carts, cart_items). - Then the right-hand region, ORDER AND MONEY: orders, order_lines and payments - the permanent half. Press play for the eight-step walkthrough; it lights each table and the FK that joins it. - Page 1 · Requirements - the guarantees the schema must give, written as sentences a constraint can enforce. - Pages 2 and 3 · Capacity and Storage - orders per second, the hot inventory row, bytes per row and index size per year. - Page 4 · Availability and integrity - constraints, the checkout transaction, isolation for the stock decrement, and what contention does to it. - Page 5 · How it works - every table walked from catalogue to payment, and why each FK has the ON DELETE it has. - Page 6 · Deployment and migrations - online schema change, backfills, partitioning and archiving. Then open Generate SQL and compare the DDL with page 5. #### 1 · Requirements **Ask these before drawing anything** - Can a guest check out? Yes here - but orders.customer_id is NOT NULL, so checkout creates (or claims, by email) a customers row. A guest is a customer with no password, not a missing foreign key. - Is the price the customer saw the price they pay? The cart reprices on every view; the order freezes. Say where the freeze happens: inside the checkout transaction, and nowhere else. - One currency per order, or mixed? One. orders.currency is NOT NULL and every line and payment must match it. - Can stock go negative (backorders)? Not on this board. If the business sells on backorder, that is a flag on the variant and a different constraint, decided up front. - Do we reserve stock while a payment is being authorised? This board does not - that is its stated hole. Decide whether the business can live with an occasional oversell and an apology email. - How long must orders be kept, and can a customer be erased? Tax law says years (six to ten depending on country); privacy law says erase on request. The answer is redaction, which is why there is no CASCADE from customers to orders. **What the schema must guarantee** - Price frozen at checkout: an order line’s unit_price_minor, sku and name never change after the order is placed, whatever happens to the catalogue. - Totals reproducible: orders.total_minor = subtotal_minor + shipping_minor + tax_minor, and subtotal_minor = the sum of its line_total_minor, checked rather than trusted. - Stock never negative: quantity_on_hand >= 0 is a CHECK, and the decrement is a single conditional UPDATE so two checkouts cannot both take the last unit. - Cart is not order: a cart may be anonymous, unpriced and expiring; an order has a customer, an address, a currency and stored totals, all NOT NULL. - Money is exact: integers of minor units with an ISO 4217 currency on every amount. - Payments are idempotent and append-only: provider_ref is UNIQUE, so a replayed webhook cannot double-credit; refunds are new negative rows, never edits. - History survives catalogue edits: delisting or deleting a variant never deletes a paid line; editing an address never moves an old parcel. **Non-functional** - Checkout (cart to placed order, excluding the payment provider’s call): p99 under 150 ms in the database. - Product and cart reads: p95 under 20 ms, served largely from replicas and cache. - Zero lost orders or payments on a failover (RPO 0 for the order half); checkout back within 2 minutes (RTO). - Every schema change applied online - no table lock longer than a couple of seconds on orders, order_lines, payments or inventory. **Out of scope** - Promotions and discount codes (they land in line_total_minor, but the rules engine is another board). - Multi-warehouse stock, shipments and returns as their own entities. - Tax calculation itself - tax_minor stores its result. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability and integrity", "5 · How it works", "6 · Deployment and migrations"; a narrated walkthrough of 8 steps; a schema of 10 tables and 64 columns. ### Multi tenant SaaS - URL: https://tierslate.com/templates/saas-multitenant-schema - Kind: database schema board; level: deep - Access: paid, 600 AI credits - the question is public, the worked answer is not Tenant isolation, and every join that could leak across it. What is hard about it, left in on purpose: `subscriptions.seats` is a number nobody enforces: no constraint ties it to the count of active rows in `memberships`, so an organisation can run twenty people on a five seat plan until billing notices. Two smaller ones are left in on purpose - `memberships` has nothing stopping the same user joining the same org twice, and an archived org keeps its unique slug forever. #### Start here **Why this design** Almost every B2B product is this schema underneath, and it is the one where a mistake is a security incident rather than a bug: a query that forgets its tenant filter shows one customer another customer's data, and nothing in development catches it because every row there belongs to the same org. Interviewers use it to see whether you can name an isolation model and defend it, whether you know where a role belongs, and whether you notice the constraints a schema cannot express. Real teams meet it twice - when they design it, and two years later when the biggest customer asks for its own database in Frankfurt. **What you will learn** - Why `org_id` is copied onto every tenant owned row - `documents` and `document_versions` included, where it is derivable - so row level security is one predicate with no join, and the drift that buys. - Shared schema, schema per tenant and database per tenant compared at 40,000 tenants, and why the answer is "shared, with an escape hatch for the whales". - Why the role lives on `memberships` and not on `users`, and why an invitation is its own table addressed to an email rather than a half-made membership. - The RLS failure modes that actually leak data: a session variable surviving into the next tenant's transaction on a pooled connection, a table owner bypassing policies, a new table shipped without one. - Why 99% of the bytes are in two append-only tables, and how org_id-leading composite indexes make a tenant export, a tenant restore and a tenant move into range scans. - What `subscriptions.seats` cannot enforce, the three ways to enforce it, and the provisioning path each one tends to miss. **How to read this board** - The tables first, left to right. GLOBAL: NOT OWNED BY ANY TENANT holds `users`, `plans` and `roles` - the rows that belong to no single org. - THE TENANT AND WHO IS IN IT: `orgs`, `memberships` and `subscriptions`, then the org scoped access records `invitations`, `api_keys` and `audit_log`. - THE TENANT'S OWN DATA: one ownership chain, `projects` -> `documents` -> `document_versions`, each carrying `org_id`. Press Play for the eight step walkthrough, which reads the tables in the order the model makes sense. - Then the written design below the tables: 1 · Requirements, 2 · Capacity, 3 · Storage, 4 · Availability, 5 · How it works (one request and one invitation, end to end, across these tables), 6 · Deployment. - The conversation to have: `subscriptions.seats` is an integer nothing enforces, so a five seat plan can hold twenty members. Two smaller ones are left in on purpose - no UNIQUE on (org_id, user_id) in `memberships`, and an archived org keeps its slug forever. #### 1 · Requirements **Ask these before drawing anything** - Can one person belong to more than one organisation with one login? For any product sold to agencies, consultants or groups of companies the answer is yes, and it decides that `users` is global and the role lives on the membership. - What does isolation have to guarantee, and to whom? "No query can return another tenant's row even if the application forgets a filter" is a database level guarantee. "Each customer's data is in a separate database" is a contractual one, and only some customers will pay for it. - Will any customer require data residency - the EU, a specific country? If yes, the tenant must be movable, and the global tables have to be thought about now. - Who is allowed to see across tenants? Support staff, billing jobs and analytics all need to, and every one of them is a deliberate, audited bypass rather than a missing filter. - Are seats a hard limit or an invoice line? A hard limit needs enforcement on every path that creates a membership; an invoice line needs only a count at the end of the period. - How long is the audit log kept, and can a tenant export it? Enterprise contracts commonly ask for one to seven years and a self-serve export, which decides its storage tier. **Functional** - Tenancy: an organisation (`orgs`) is the unit of isolation, billing and administration. Every tenant owned row records the org it belongs to. - Accounts: one global `users` row per email. Password or SSO; a person can be a member of many orgs and switches between them without signing in again. - Roles: four built in roles every org gets - owner, admin, member, billing - plus custom roles an org defines for itself. A role is held per membership, never per user. - Invitations: an admin invites an email address AS a role; the link expires after 7 days; acceptance creates the account if needed and the membership in one transaction, and records which membership the invitation became. - Billing: one live subscription per org on a plan from a global catalogue, with a purchased seat count. Suspended members still hold a seat. - API keys: a machine credential that acts as a specific member and inherits that membership's role; revoking the member revokes the keys. - Audit: every mutating action and every security event (sign-in, role change, key creation, invitation) is recorded against the acting membership, survives that member being removed, and is exportable by the tenant. - Tenant data: projects, documents and an immutable version history of each document. **Non-functional** - Isolation: zero cross tenant reads or writes, enforced by the database (row level security) and not only by application code. This is the requirement every other one gives way to. - Authorisation on every request - resolve the membership and role for (org, user) - under 2 ms p99 including cache. - Membership or role revocation takes effect within 60 seconds on every node. - Application reads p95 under 150 ms; document save p95 under 250 ms. - A tenant can be exported, restored to a point in time, moved to another database or region, and deleted, without touching any other tenant. - No single tenant can take more than its share: one org's bulk export must not move another org's p99. **Out of scope** - Payment collection and invoicing - the billing provider owns them; `provider_customer_id` is the seam. - SSO and SCIM provisioning, except as another path that creates memberships (and so another path that must respect seats). - Hierarchical organisations (a parent company with child orgs). A real request, and a different board: it turns `org_id` from a value into a tree. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 8 steps; a schema of 12 tables and 74 columns. ### Learning platform - URL: https://tierslate.com/templates/lms-schema - Kind: database schema board; level: core - Access: paid, 400 AI credits - the question is public, the worked answer is not Courses, lessons, enrolments and the progress that must survive a course being edited. What is hard about it, left in on purpose: lesson_progress points at the stable lesson, not the lesson_revision the learner actually saw, so completion survives every edit - including a rewrite of a compliance lesson that legally requires everyone to redo it. Nothing records which revision was completed, so "everyone who finished the old safety module must take the new one" can be neither enforced nor audited #### Start here **Why this design** Every course platform - Udemy, Coursera, corporate training, a bootcamp's own site - has the same four nouns: courses, lessons, enrolments, progress. The schema looks obvious until the first time an author edits a live course. Insert a lesson at position 3 and progress keyed on position now points at the wrong lessons for every learner; overwrite a lesson row and the record of what a learner completed now describes content they never saw. This board is about separating what authors edit from what learners did, so that each can change without corrupting the other. **What you will learn** - Why a lesson is two tables - a stable identity (lessons) and immutable content (lesson_revisions) - and progress points at the identity. - Why a published course_version is a frozen snapshot of its curriculum, and an enrolment pins the version it started on. - Why curriculum_items can use an integer position while a kanban board cannot: nobody reorders a published list. - Why progress is one upserted row per (enrolment, lesson) instead of a log of player heartbeats, and why completion only moves forward. - How a certificate records the version it certifies, so it stays true after the course changes. - The deliberate hole: progress cannot say which revision was completed, so a mandatory re-take cannot be expressed. **How to read this board** - CONTENT along the top: courses, course_versions, curriculum_items, lesson_revisions, lessons. - Then PEOPLE AND ENROLMENT (users, enrolments, certificates) and PROGRESS (lesson_progress, quiz_attempts). Press play for the walkthrough. - Page 1 · Requirements - the guarantees, written so a constraint can enforce them. - Pages 2 and 3 · Capacity and Storage - heartbeats, progress rows and why the content tables are tiny. - Page 4 · Availability and integrity - constraints, the publish transaction and the one-way completion rule. - Page 5 · How it works, then 6 · Deployment and migrations. #### 1 · Requirements **Ask these before drawing anything** - Can authors edit a course while people are enrolled? Always yes, and it is the requirement everything else bends around. - When a course changes, do existing learners see the new version, the old one, or choose? The usual answer: new learners get the new version, existing learners stay unless they opt in or an admin migrates them. - Is any course compliance training, where completing an outdated version does not count? - Are certificates verifiable by a third party, and must they stay valid after the course changes? - Is progress per lesson only, or within a lesson (video position, quiz attempts)? **Guarantees, as sentences a constraint can enforce** - A learner is enrolled in a course at most once at a time: unique (user_id, course_id) where status is active. - An enrolment always refers to a published version of the same course it is enrolled in. - A published version's curriculum never changes: curriculum_items are insert-only once the version has published_at. - There is at most one progress row per (enrolment, lesson). - completed_at, once set, is never cleared or moved later. - A certificate refers to exactly one enrolment and the version that enrolment completed. **Out of scope** - Payments and pricing - an enrolment may be created by a purchase, but the order lives elsewhere. - Video hosting and transcoding - lesson_revisions holds a reference, not bytes. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability and integrity", "5 · How it works", "6 · Deployment and migrations"; a narrated walkthrough of 8 steps; a schema of 10 tables and 62 columns. ### Warehouse inventory - URL: https://tierslate.com/templates/inventory-schema - Kind: database schema board; level: core - Access: paid, 400 AI credits - the question is public, the worked answer is not Stock by location, movements as rows, and why on-hand is a sum rather than a field. What is hard about it, left in on purpose: stock_balances is a cached sum of stock_movements kept in the same transaction, so nothing in the schema proves the two agree - only a reconciliation job does - and every pick of a hot SKU serialises on one balance row; reservations are held per warehouse, not per location or lot, so a reservation can be "available" on paper and unpickable because the only units left are in quarantine or expire tomorrow #### Start here **Why this design** Every inventory system starts with a quantity column and every one that survives contact with a real warehouse ends up with a ledger. The reason is not elegance, it is auditing: the number on the screen disagrees with the shelf at least once a day, and the only question that matters then is "which event made it wrong". A field that is overwritten cannot answer that. A table of movements can, for every unit, forever. Interviewers use this question to see whether you separate facts (something moved) from derived state (how many are here now), and whether you know where the derived state is allowed to be wrong. **What you will learn** - Why stock_movements is append-only and on-hand is the sum of its rows, and why a correction is a new movement the other way rather than an UPDATE. - Why every movement has a from AND a to location, with virtual supplier, customer and loss locations, so that every item nets to zero across the whole system. - How stock_balances keeps reads cheap as a projection written in the same transaction, and the one query that proves it has not drifted. - Why a reservation is its own row with an expiry, and why available-to-promise is on_hand minus reserved, never a third stored number. - Why lots and expiry dates turn "pick any unit" into "pick the one expiring first" (FEFO), and how a cycle count becomes an adjustment movement. - How a hot SKU serialises on one balance row, and the bucketed balance that relieves it. **How to read this board** - Top row: WHERE (warehouses, locations - including the virtual ones) and WHAT (items, lots). - Bottom left: THE LEDGER - stock_movements, the only table that records a fact, and cycle_counts, which feeds it. - Bottom right: DERIVED - stock_balances and reservations, both of which can be rebuilt or expired. - Press play for the walkthrough; it lights each table and the foreign key that joins it. - 1 · Requirements, 2 · Capacity, 3 · Storage, 4 · Availability and integrity, 5 · How it works, 6 · Deployment and migrations. Then Generate SQL and read the DDL against page 5. #### 1 · Requirements **Ask these before drawing anything** - One warehouse or many? Many here, each with its own locations. Transfers between warehouses are two movements through an "in transit" virtual location, because the truck is a place where stock is for two days. - Are items lot- or serial-tracked? Lot-tracked where the item says so (food, pharma, anything with a recall). Serial tracking, one row per physical unit, is out of scope and would replace quantity with identity. - What is the unit of measure, and can it be fractional? Integers of the base unit (each, gram, millimetre). A case of 12 is a packaging fact, converted at receipt, never stored as 0.0833 of something. - Who reserves stock, and for how long? Sales orders, for 30 minutes at checkout and until pick once paid. A reservation that nobody releases must expire on its own. - How often are counts done? Cycle counting, a slice of locations every night, rather than an annual wall-to-wall stocktake that closes the warehouse. - Is negative stock allowed? Never on a real location. If the system says pick 3 and the shelf has 2, the pick shorts and a count is triggered - the database refuses to pretend. **What the schema must guarantee** - Every change in quantity is a stock_movements row: who, when, why, from where, to where, how many. Nothing else changes a quantity. - Movements are never updated or deleted. A wrong movement is reversed by an equal and opposite one that references it. - For every item, the sum of balances over all locations, virtual ones included, is zero. - A real location never has a negative balance; the CHECK is on stock_balances and the movement insert fails if it would be violated. - Available to promise is computed, never stored: on_hand minus active reservations. - A replayed scanner event cannot double-move stock: idempotency_key is UNIQUE. **Non-functional** - Pick confirmation (one movement + one or two balance updates): p99 under 30 ms in the database. - Available-to-promise for one SKU across a warehouse: p95 under 10 ms, because checkout calls it on every cart view. - Zero lost movements on failover - a lost movement is stock that exists on the shelf and nowhere else. **Out of scope** - Purchase orders and supplier management (a receipt references one by id). - Slotting and pick-path optimisation - which location an item SHOULD live in is a different problem from where it is. - Costing (FIFO / weighted average valuation). It reads this ledger; it is a finance board. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability and integrity", "5 · How it works", "6 · Deployment and migrations"; a narrated walkthrough of 8 steps; a schema of 8 tables and 63 columns. ## AI & LLM systems ### RAG chatbot - the answer is only as good as the retrieval - URL: https://tierslate.com/templates/rag-chatbot - Kind: system architecture board; level: core - Access: paid, 400 AI credits - the question is public, the worked answer is not Why a document chatbot is a search system feeding a model, and how chunking and citations decide trust. What is hard about it, left in on purpose: PERMISSIONS ARE COPIED INTO THE INDEX AT INGEST TIME AND THE BOARD DOES NOT FIX IT. Each chunk carries the ACL groups its document had when it was last indexed, and retrieval filters on that copy - so somebody removed from a group can still retrieve that group's chunks until the next re-index, which is hours for a changed document and never for an unchanged one unless permission changes are themselves change events. The second unresolved thing is that RETRIEVAL FAILURE IS SILENT: when the right chunk is not in the top eight, the model still writes a fluent answer from the wrong ones, and the grounding check here only verifies that each cited sentence is supported by SOME retrieved chunk, not that the right chunk was retrieved. And changing the embedding model means re-embedding all 4.5 million chunks and running two indexes side by side, because vectors from two models are not comparable. #### Start here **Why this design** "Build a chatbot over our internal documents" is now the most common applied-AI system design question, and most answers spend their time on the model. The model is the part you cannot change and do not need to: given the right eight passages, any current frontier model writes a good answer. The design work is retrieval - how documents are cut into chunks, how a follow-up question becomes a searchable query, why vector search alone misses exact terms, how fifty candidates become eight - and trust: citations a reader can check, permissions that stop the bot quoting a document the asker cannot open, and an evaluation that measures retrieval separately from writing. **What you will learn** - Why chunk size (here 512 tokens with 64 overlap) is the most consequential parameter in the system, and why chunks carry their heading path. - Why hybrid retrieval - dense vectors AND BM25, merged with reciprocal rank fusion - beats either alone on internal documents full of product codes and acronyms. - Why a cross-encoder reranker turns 50 candidates into 8, and why it is affordable only because retrieval already cut ten million to fifty. - How the prompt is assembled with numbered sources so the answer can cite [1]..[8], and what a grounding check can and cannot prove. - Where the money goes (input tokens, not output), where the latency goes (time to first token), and why the 6,000-token prompt is the bill. - Why permissions copied into the index go stale, and why an embedding-model upgrade is a full re-index. **How to read this board** - Press Play and follow the online path left to right: THE QUESTION, RETRIEVE, GROUND. - Then the bottom row, INGEST AND EVALUATE: the offline pipeline that decided, weeks ago, what could be found at all. - 1 · Requirements, 2 · Capacity (tokens and latency), 3 · Storage (vectors), 4 · Availability, 5 · How it works, 6 · Deployment. - Finally the DATA MODEL, and the table that logs what was retrieved for every answer - the one that makes evaluation possible. #### 1 · Requirements **Ask these before drawing anything** - What are the sources, and how fast do they change? A wiki edited hourly, a drive of PDFs, a ticket system. Change rate sets the ingest design; "re-index everything nightly" works at 50,000 documents and not at 500,000. - Who may see what? If the answer is "everyone sees everything", half this board disappears. It never is. Retrieval must filter by the asker's groups BEFORE ranking, never after generation. - Must every answer cite? Yes for anything internal: a cited answer can be checked in five seconds, an uncited one cannot be trusted at all. - What should happen when the documents do not contain the answer? "I could not find this in the documents" is a correct answer and must be a designed outcome, not a failure. - Is it single-turn search or a conversation? A conversation means follow-ups like "and for contractors?", which are not searchable on their own and need rewriting. - How will quality be measured? If there is no golden set of questions with known source passages, there is no way to tell whether a chunking change helped. **Functional** - Answer natural-language questions over 500,000 documents from four source systems, citing every claim with a numbered link to the passage it came from. - Respect source permissions: never retrieve a chunk whose document the asker cannot open in the source system. - Support follow-up questions within a conversation, using the last six turns. - Reflect a document edit within 15 minutes and a deletion within 5. - Log, for every answer, every chunk retrieved, its rank, whether it was cited, and the user's feedback. **Non-functional** - Time to first token p95 under 1.5 s; full answer p95 under 8 s. Streaming is not a nicety - it is what makes an 8-second answer feel fast. - Retrieval recall@8 above 0.85 on the golden set, measured on every chunking, embedding or reranker change. - Under 2 percent of answers containing a citation that does not support its sentence. - Cost under 3 cents per answer at list prices. **Out of scope** - Agents that take actions (filing a ticket, sending an email). That is tool use with its own authorisation problem. - Fine-tuning the generator on the corpus. It teaches style, not facts, and cannot cite. - Structured data questions ("how many tickets last week") - those want text-to-SQL, a different board. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 9 steps; a schema of 5 tables and 39 columns. ### LLM gateway - one API in front of every model provider - URL: https://tierslate.com/templates/llm-gateway - Kind: system architecture board; level: core - Access: paid, 400 AI credits - the question is public, the worked answer is not How prompts are routed, cached, metered and failed over across providers, and why tokens are the unit of cost. What is hard about it, left in on purpose: A BUDGET CANNOT BE ENFORCED EXACTLY ON A STREAM AND THE BOARD DOES NOT PRETEND OTHERWISE. Admission reserves estimated input plus max_tokens, but the estimate uses a local tokenizer that differs from each provider's by several percent, and callers set max_tokens high "to be safe", so either reservations over-hold capacity (and a team is throttled while half its budget sits in holds) or they are trimmed and concurrent streams overshoot the limit before any of them settles. The second unresolved thing is FAILOVER: a request failed over to another provider is counted with a different tokenizer, priced differently, answered by a different model, and loses the provider-side prompt cache - so an outage at the primary can triple the input bill for its duration, and nothing here caps that. And a stream that has already sent tokens to the caller cannot fail over at all without the caller seeing the answer restart. #### Start here **Why this design** Every company with more than a handful of LLM features ends up building this, usually after the first surprise invoice. Forty services each holding their own provider keys means nobody can answer "what did the support bot cost last week", one runaway batch job can exhaust the organisation's tokens-per-minute limit and take every other feature down with it, and an outage at one provider is an outage everywhere. The gateway puts one API in front of all of it. Interviewers like it because it looks like an ordinary API gateway and is not: the unit is the token, cost is only known after the response, and failover changes the product. **What you will learn** - Why rate limits and budgets are in tokens per minute and dollars, not requests, and how a reservation is placed before a call and settled after it. - Why the gateway meters from the provider's reported usage rather than its own tokenizer, and stores cost in integer micro-dollars with the price snapshotted. - How routing works: model aliases, a primary and a fallback per alias, circuit breakers per provider and model, and why failover is a behaviour change. - What caching can do here: exact-match response caching (rarely hits for chat), and provider-side prompt caching of long shared prefixes (hits constantly, and is lost on failover). - Why the gateway must add under 10 ms and never buffer a stream. **How to read this board** - Press Play and follow a request: CALLERS, THE GATE, ROUTE - and back out as a stream. - Then METER along the bottom: usage events, the priced ledger, the per-team chargeback, and the dashed wire that settles a reservation. - 1 · Requirements, 2 · Capacity (the token arithmetic), 3 · Storage, 4 · Availability, 5 · How it works, 6 · Deployment. - Then the DATA MODEL, where cost is an integer and the price is copied onto every usage row. #### 1 · Requirements **Ask these before drawing anything** - How many providers, and are any models self-hosted? Two hosted providers plus a self-hosted open-weights model for cheap bulk work, here. Each has a different API, tokenizer, price and rate limit. - Is the API one shape for all of them? Yes - one chat-completions-style request and streaming response, translated per provider. Callers ask for an ALIAS ("fast", "smart", "embed"), never a vendor model string, so routing can change without a deploy anywhere else. - Who pays? Every request carries a virtual key that belongs to a team; every token is charged back to that team monthly. - Hard or soft budgets? Soft at 80 percent (alert), hard at 100 percent (reject) for most teams; production-critical keys get an overdraft. - May prompts be logged? Not by default. Prompts contain customer data; the gateway logs token counts and metadata, and full bodies only for keys that opt in, redacted, for 7 days. - Is failover allowed to change the model? It has to be, since no two providers serve the same model. Each alias declares which fallbacks are acceptable. **Functional** - One OpenAI-compatible endpoint for chat, embeddings and batch, translated to each provider. - Authenticate virtual keys; map key to team, allowed aliases and limits. - Enforce per-key and per-team tokens-per-minute and monthly spend limits. - Route each alias to a primary model with ordered fallbacks; fail over on 429, 5xx, connection errors and time-to-first-token timeouts. - Meter input, cached input and output tokens per request from the provider's usage report, price them, and attribute the cost to a team within a minute. - Optional exact-match response cache, opt-in per key, for deterministic (temperature 0) workloads. **Non-functional** - Added latency under 10 ms p99 before the first byte goes to the provider; zero added latency per streamed token. - Availability 99.95 percent for the gateway itself - higher than any single provider, which is the point of routing. - Metering loss under 0.01 percent of tokens, reconciled against provider invoices monthly within 1 percent. **Out of scope** - Prompt management and versioning (a different tool upstream). - Evaluation of output quality - the gateway knows the cost of an answer, not whether it was good. - Agent orchestration. Tool calls pass through as ordinary messages. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 9 steps; a schema of 5 tables and 41 columns. ### Vector search - nearest neighbours, approximately - URL: https://tierslate.com/templates/vector-search - Kind: system architecture board; level: deep - Access: paid, 600 AI credits - the question is public, the worked answer is not Why similarity search uses an approximate index that trades recall for speed, and how filters break it. What is hard about it, left in on purpose: MID-SELECTIVITY FILTERS ARE NOT SOLVED HERE. The planner brute-forces filters that match under 0.1 percent of a shard and post-filters ones that match over 20 percent, and in between it searches the IVF index while skipping non-matching vectors - which, for a filter matching 2 percent of vectors clustered in a few lists, returns three results instead of ten or ten wrong ones, at full latency. Nothing alerts on it because recall is measured on a 1-in-1,000 sample of unfiltered-looking traffic. The second unresolved thing is DRIFT: the IVF centroids were trained on last quarter's data, new content lands in a few overfull lists, and both latency and recall degrade gradually until somebody schedules a full re-train and rebuild of 1 billion vectors. And deletes are tombstones, so a collection with heavy churn searches through dead vectors until compaction runs. #### Start here **Why this design** Every retrieval-augmented chatbot, semantic search box, "more like this" rail and duplicate detector sits on a vector index, and "design a vector database" has become a standard deep system design question. It is a good one because the naive answer - store the vectors, compute distances - is correct and useless, and the real answer is a set of trade-offs with numbers on them: memory against recall, recall against latency, freshness against index quality, and filters against all three. Interviewers want to hear "approximate", then "recall@k", then which index and why it fits, then what a WHERE clause does to it. **What you will learn** - Why exact kNN over 1 billion x 768 dimensions is 3 TB scanned per query, and what recall@10 = 0.95 actually promises. - How HNSW (a layered navigable graph) and IVF-PQ (k-means clusters plus product-quantised codes) work, and why 96-byte codes plus NVMe rescoring beats 3.3 TB of RAM here. - How nprobe, ef_search and the rescore depth move you along the recall/latency curve. - Why filters break ANN, and how a planner chooses between brute force, filtered search and post-filtering from selectivity. - Why writes are an LSM: a mutable HNSW segment, sealed immutable IVF-PQ segments, tombstones and compaction. - Why scatter-gather over 16 shards makes p99 a tail-of-tails problem, and why hedged requests fix it. - Why recall must be sampled continuously against exact search, or nobody knows it has dropped. **How to read this board** - Press Play: ASK, PLAN, SEARCH (one shard of sixteen drawn), and the MEASURE box that is the only thing that knows if the answers are right. - Then the bottom row, WRITE: log, mutable segment, seal, build, publish, compact. - 1 · Requirements, 2 · Capacity, 3 · Storage, 4 · Availability, 5 · How it works, 6 · Deployment. - Then the DATA MODEL - the control plane that knows which segments, built with which centroids, make up each shard. #### 1 · Requirements **Ask these before drawing anything** - How many vectors, how many dimensions, and how fast does the set grow? 1 billion, 768-d, 10,000 upserts a second here. At 1 million vectors a flat scan in one process is fine and none of this board is needed. - Which distance? Cosine, dot product or L2. Normalise at write time and cosine becomes dot product, which every index handles best. - What recall is acceptable, and is anyone measuring it? 0.95 recall@10 is a common target. A team that cannot state its recall has an index whose quality is unknown. - What filters, and how selective? Tenant, language, date, access group. "Most queries filter to one tenant, and the largest tenant is 8 percent of the data" changes the design more than any other answer. - How fresh must a write be? Searchable in a second (mutable segment) or in an hour (batch build)? The cost difference is a whole layer. - Do deletes have to be immediate? For a right-to-erasure request, yes - which is why tombstones are checked at query time rather than waiting for compaction. **Functional** - Upsert and delete vectors with an external id, a tenant and attributes; searchable within 2 seconds. - Query: top-k (k <= 100) nearest by cosine, with an optional filter expression over attributes, returning ids, scores and attributes. - Per-collection configuration of dimensions, metric and index parameters; one embedding model per collection. - Online rebuilds: re-train centroids and rebuild all segments without stopping queries. **Non-functional** - 5,000 queries per second; p50 15 ms, p99 50 ms inside the service. - recall@10 >= 0.95 on unfiltered queries and >= 0.90 on filtered ones, measured on sampled live traffic against exact search. - No acknowledged write lost (it is in the log before the ack). Deleted vectors never returned after the delete is acknowledged. - Losing a node degrades latency, never correctness: every shard has two replicas. **Out of scope** - Producing the embeddings - the caller does, or the query API calls a model for text queries. - Hybrid lexical search (BM25) - often fused with this, but a separate index. - Cross-collection joins. A vector index is not a database and should not pretend to be one. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 9 steps; a schema of 6 tables and 47 columns. ### LLM inference serving - the GPU is the bill - URL: https://tierslate.com/templates/llm-inference-serving - Kind: system architecture board; level: deep - Access: paid, 800 AI credits - the question is public, the worked answer is not How batching, KV cache and streaming turn a scarce GPU into many conversations, and why latency fights throughput. What is hard about it, left in on purpose: INTERACTIVE AND BATCH TRAFFIC SHARE REPLICAS AND THE BOARD DOES NOT SEPARATE THEM. Priority is enforced only at admission, so once a 30,000-token batch request is admitted its prefill - chunked to 512 tokens per step, but still about sixty steps - slows every interactive stream on that replica, and time per output token for chat users roughly doubles while it runs. Splitting the fleet (or disaggregating prefill onto its own GPUs) fixes that and strands capacity whenever either pool is idle. The second unresolved thing is SCALE-UP: a new replica takes 3 to 8 minutes to pull 70 GB of weights and warm up, so a burst either queues for minutes or is absorbed by GPUs that were paid for while idle - and at roughly twelve dollars an hour per replica, the headroom IS the bill. And prefix-affinity routing, which is what makes the prefix cache hit, sends every request with a popular system prompt to the same few replicas, trading cache hits for hot spots. #### Start here **Why this design** Calling a hosted model is an API problem. Serving your own - for cost at volume, for data that cannot leave, or for a fine-tuned open-weights model - is a hardware economics problem, and it is the deepest AI system design question there is. A GPU costs the same per hour idle or busy, its memory holds only so many conversations, and the two phases of generation stress it in opposite ways. Interviewers want to hear the arithmetic: how big the KV cache is per token, why decode is memory-bound, what continuous batching and paged attention buy, and what you would trade to get time-to-first-token down. **What you will learn** - How to size GPU memory: 70 GB of FP8 weights, about 206 GB of KV cache on four H100s, and 320 KB of KV per token - so about 640,000 tokens, or ~250 concurrent conversations. - Why prefill is compute-bound and decode is bandwidth-bound, and what that means for TTFT (time to first token) and TPOT (time per output token). - How continuous batching admits and retires sequences every step, and how paged KV blocks make that possible without fragmentation. - How prefix caching, chunked prefill and speculative decoding each move the latency/throughput line - and when each one stops helping. - How preemption works when the KV cache fills, how routing exploits the prefix cache, and why autoscaling GPUs is slow. - How to price it: tokens per GPU-hour, and why utilisation sets the cost per million tokens. **How to read this board** - Press Play: THE REQUEST, ROUTE AND ADMIT, then ONE REPLICA - prefill, the decode loop, the KV cache and what happens when it fills. - Then FLEET along the bottom: the metrics that drive scaling, and the minutes a new replica takes to exist. - 1 · Requirements, 2 · Capacity (the GPU arithmetic), 3 · Storage (memory), 4 · Availability, 5 · How it works, 6 · Deployment. - Then the DATA MODEL: every request's tokens, cached tokens, TTFT and TPOT, which is how a GPU bill becomes a per-tenant one. #### 1 · Requirements **Ask these before drawing anything** - Which model, and at what precision? A 70B dense model with grouped-query attention (80 layers, 8 KV heads of 128 dims), served in FP8. Precision halves memory and roughly doubles compute throughput on H100s; it must be validated against quality, not assumed. - What are the latency targets, and which matters more? TTFT is what makes a chat feel responsive; TPOT is reading speed. Humans read at 5-10 tokens a second, so 25+ tokens a second per stream is "fast"; agents and batch jobs care about total throughput instead. - What is the context-length distribution? Average 2,000 in and 400 out, but a long tail to 32,000. The tail decides KV capacity and the worst prefill stall. - How much of each prompt is shared? Long system prompts and few-shot examples repeat across requests - here about 60 percent of input tokens. That is what prefix caching is worth. - Interactive, batch, or both on one fleet? Both, here, with priority at admission - the board's tension. - What GPUs can you actually get, and how fast? Capacity is often reserved months ahead; autoscaling only works inside what was reserved. **Functional** - OpenAI-compatible chat completions with streaming (server-sent events), stop sequences, max_tokens and temperature. - Per-tenant limits on concurrent sequences and tokens per minute, with interactive above batch priority. - Report prompt, cached-prompt and output tokens per request for metering. - Cancel generation when the client disconnects, freeing its KV blocks within one step. **Non-functional** - Interactive: TTFT p95 under 800 ms for prompts under 4,000 tokens; TPOT p95 under 40 ms (25+ tokens/s per stream). - Throughput: at least 8,000 output tokens/s per four-GPU replica at the interactive latency targets. - Fleet utilisation above 60 percent of GPU-hours over a day - below that, a hosted API is usually cheaper. - No request silently truncated: a sequence preempted for memory resumes or fails explicitly. **Out of scope** - Training and fine-tuning (another cluster, another scheduler). - The model's quality, safety filtering and prompt design. - Multi-region active-active - this fleet is one region; the gateway in front fails over to a hosted provider. Withheld until the board is owned: the pages "2 · Capacity estimation", "3 · Storage estimation", "4 · Availability", "5 · How it works", "6 · Deployment plan"; a narrated walkthrough of 10 steps; a schema of 6 tables and 51 columns. --- # Mockups in full Every mockup example is free to open and edit in Tierslate. Its React code (CSS or Tailwind) can be viewed on any plan; copying or downloading it is part of Pro. ## Food delivery app - URL: https://tierslate.com/mockups/food-delivery-app - Platform: mobile - Screens (4): Home, Restaurant menu, Cart, Order tracking Browse restaurants, pick dishes, check out and follow the courier - four mobile screens. ## Shopping website - URL: https://tierslate.com/mockups/shopping-website - Platform: web - Screens (4): Home, Product listing, Product detail, Cart & checkout An online clothing store - home page, product listing with filters, product detail and checkout. ## Banking app - URL: https://tierslate.com/mockups/banking-app - Platform: mobile - Screens (3): Accounts, Transaction detail, Send money A mobile bank - balance with quick actions and recent activity, a transaction detail and a send-money flow. ## Admin dashboard - URL: https://tierslate.com/mockups/admin-dashboard - Platform: web - Screens (2): Dashboard, Customer detail A store back office - KPI cards, revenue chart, top products and recent orders, plus a customer profile page. ## Social feed - URL: https://tierslate.com/mockups/social-feed - Platform: mobile - Screens (3): Feed, New post, Profile A photo-sharing app - stories and a post feed, a new-post composer and a profile with stats and a photo grid. ## Travel booking - URL: https://tierslate.com/mockups/travel-booking - Platform: web - Screens (3): Search, Results, Booking A hotel booking site: search with dates and guests, filtered results with hotel cards, and a guest details + payment checkout. ## SaaS landing page - URL: https://tierslate.com/mockups/saas-landing-page - Platform: web - Screens (1): Landing page A full marketing page for a project-management product: hero with two calls to action, features, pricing tiers, a testimonial, FAQ and footer. ## Chat messenger - URL: https://tierslate.com/mockups/chat-messenger - Platform: desktop - Screens (2): Inbox, Settings A desktop messaging app: conversation list with unread badges, a group thread with message bubbles and attachments, and a notifications settings dialog. ## Music player - URL: https://tierslate.com/mockups/music-player - Platform: mobile - Screens (2): Library, Now playing A mobile music app: a library with playlists and recently played tracks, and a now-playing screen with album art, progress and playback controls. ## Sign in and sign up flows - URL: https://tierslate.com/mockups/auth-flows - Platform: web - Screens (3): Sign in, Sign up, Forgot password The three account screens every product needs: sign in with social options, create an account with a password strength meter, and reset a forgotten password. ## iOS fitness app - URL: https://tierslate.com/mockups/ios-fitness-app - Platform: mobile - Screens (5): Summary, Workouts, Workout detail, Settings, New workout An iPhone activity tracker in the iOS 17/18 look: large-title navigation, activity rings, widgets, inset grouped lists, a tab bar, a settings sheet, an action sheet and an alert over the keyboard. ## Android mail app - URL: https://tierslate.com/mockups/android-mail-app - Platform: mobile - Screens (6): Inbox, Navigation drawer, Message, Compose, Schedule send, Tablet · schedule send A Material 3 (Material You) email client: search bar, filter chips, inbox list, FAB, navigation drawer, message view, compose with a dialog, a schedule-send bottom sheet, and a tablet layout with a navigation rail and date/time pickers. ## Ride hailing app - URL: https://tierslate.com/mockups/ride-hailing-app - Platform: mobile - Screens (3): Where to, Choose a ride, Driver on the way A mobile ride app: pick up and destination over a map, a choice of ride types with prices, and a live driver-on-the-way screen. ## Video streaming site - URL: https://tierslate.com/mockups/video-streaming-site - Platform: web - Screens (3): Browse, Title detail, Player A streaming service - browse rows with continue watching and trending titles, a series detail page with episodes, and the video player. ## Project board - URL: https://tierslate.com/mockups/project-board - Platform: web - Screens (2): Board, Task detail A kanban project tool - columns of task cards with tags, progress and assignees, and a task detail page with subtasks, activity and properties. ## Calendar app - URL: https://tierslate.com/mockups/calendar-app - Platform: desktop - Screens (3): Week view, Event editor, Month view A desktop calendar - a colour-coded week view with a mini month and calendar toggles, an event editor with guests, and a month view with an agenda. ## Real estate listings - URL: https://tierslate.com/mockups/real-estate-listings - Platform: web - Screens (2): Search results, Listing detail A home search site - filters, listing cards beside a map, and a listing page with a photo gallery, key facts and a tour request form. ## Recipe app - URL: https://tierslate.com/mockups/recipe-app - Platform: mobile - Screens (3): Discover, Recipe detail, Cooking mode A mobile cooking app: discover recipes by category, a recipe page with ingredients and servings, and a step-by-step cooking mode with a timer. ## CRM dashboard - URL: https://tierslate.com/mockups/crm-dashboard - Platform: web - Screens (2): Pipeline, Contact detail A sales CRM: a pipeline board with KPIs and deal cards per stage, and a contact page with the open deal, activity timeline, tasks and call logging. ## Onboarding flow - URL: https://tierslate.com/mockups/onboarding-flow - Platform: mobile - Screens (3): Welcome, Permissions, Profile setup A mobile app’s first-run flow: a welcome screen, a permissions step with toggles and a notification preview, and a profile setup form. ## Note-taking app - URL: https://tierslate.com/mockups/note-taking-app - Platform: desktop - Screens (2): Editor, Share note A desktop notes app: notebooks and tags in a sidebar, a note list, a rich editor with checklist and table, and a share dialog with access controls. ## Smart home app - URL: https://tierslate.com/mockups/smart-home-app - Platform: mobile - Screens (3): Home, Living room, Thermostat A mobile smart home controller: rooms at a glance with scenes and a door lock, a room screen with light and device controls, and a thermostat with schedule.