"It's AP." Two words, and the interviewer already knows how deep your understanding goes. CAP is the most-cited and least-understood idea in a system design interview. Three specific misunderstandings show up over and over, and each one produces a worse answer than saying nothing at all. Availability does not mean "the data is there." In CAP, availability means only that the system returns a successful, non-error response. It says nothing about whether that response is current. An available system will hand you a stale account balance with total confidence. CAP consistency is not ACID consistency. CAP consistency is about what values reads can return across replicas. ACID consistency is about preserving invariants inside a transaction. Same word, unrelated concepts — and conflating them is a visible tell. CAP is not a permanent "pick two." It describes the trade-off during a network partition, not a standing property of your system. Partitions are effectively unavoidable, which is exactly what makes this a practical design question instead of a theoretical one. Here's what the trade-off actually looks like when it's concrete: A bank balance should reject requests rather than risk a double-spend. A shopping cart or a likes counter should keep accepting updates and reconcile conflicts later. Same architecture, opposite call — and the reason is the business consequence of being wrong. So instead of "it's AP," try the sentence that shows you've made a decision: "During a partition, this subsystem should reject writes, because the cost of a stale read here is X." And the part that reads as senior: one product usually needs both. Naming which subsystem gets which is the move. Full breakdown, including what to say when the follow-up comes: → https://fm.dev/4qpPVxf
Formation
Professional Training and Coaching
San Francisco, California 16,225 followers
Formation is the world's first AI-powered platform helping experienced software engineers land lifechanging tech roles.
About us
Formation is the world’s only AI-powered dynamic interview prep platform, providing unlimited benchmarking, personalized skill development, and world-class mentorship to accelerate peoples' engineering careers. Since June 2020, with Formation’s hyper-personalized learning approach, 550+ engineers have found success landing at companies across the tech industry. Some of these companies include Meta, Google, Twitch, Dropbox, Adobe and Figma among many others. Formation knows that every engineer is coming to the table with different experiences and a different way of learning. Formation's patented adaptive learning algorithm takes that into account and creates a dynamic interview prep roadmap to focus an engineer's efforts on the exact skills they need to develop at the exact right time. Thanks to this hyper-focused and personalized approach, Formation has successfully helped shape peoples' career paths for those with minimal experience to 17+ years of experience, unlocking prestigious roles at top-tier companies for ambitious engineers.
- Website
-
https://formation.dev
External link for Formation
- Industry
- Professional Training and Coaching
- Company size
- 11-50 employees
- Headquarters
- San Francisco, California
- Type
- Privately Held
- Founded
- 2019
- Specialties
- Computer Science Education
Employees at Formation
Locations
-
Primary
Get directions
San Francisco, California, US
Updates
-
"I'd shard the database." One follow-up question punctures that answer: shard by what? A partition key is not a technical detail. It's a statement about which access patterns matter — and it's the hardest thing in your system to change later. Choosing one means choosing which queries you make cheap and which you accept as permanently expensive. There is no universally correct key. There are only trade-offs you can name: Shard by parent ID (advertiserID, tenantID). Co-locates related data, avoids distributed transactions, makes "everything for this tenant" a single-shard query. Breaks any lookup that doesn't already know the parent ID. Risks badly uneven load. Shard by the entity's own ID. Beautiful even distribution for point lookups. Turns every relational query — "all campaigns for this advertiser" — into a scatter-gather fan-out across every shard. Shard by timestamp or range. Great for time-range scans. Puts a hotspot on the newest partition, which is exactly where all your write traffic lives. Three things that separate a senior answer: The jumbo tenant. One dominant customer outgrows its shard. Now you're doing composite keys or tenant splitting, under pressure, on live data. Name that risk before the interviewer does. Hot shards and hot keys are different problems. Hashing fixes uneven shard sizes. It does nothing for one wildly popular key. Constantly conflated. The resharding tax. Growing a sharded database is a migration project, not a config change. Nobody mentions this upfront and everybody pays it. Anyone who says "shard by ID" without naming what that breaks hasn't made a decision. They've picked a default. The full reference piece — three strategies, the trade-offs, and a four-question checklist for choosing a key live in an interview: → https://fm.dev/3U6T3Sz
-
"If the leader dies, we promote a follower." Then: how do you know it died? And who decides? Failover sounds like an operation. It's a distributed consensus problem wearing a simple name — and done carelessly it produces two failures worse than the outage it was meant to prevent. Split-brain. Two nodes both believe they're the leader. Both accept writes. Both are correct from where they're standing. You now have two divergent histories of the same data and no automatic way to merge them. The zombie leader. The old leader was never dead, only unreachable. It comes back, still believing it's in charge, and overwrites newer data written to the new leader. The dangerous word there is silently. Nothing errors. No alert fires. You find out later, from the data. Why almost nobody catches these before production: each one needs a partition, plus a promotion, plus a specific ordering of events. They don't reproduce in staging. What actually prevents them — fencing tokens, quorum-based leader election, STONITH — all reduce to one principle: a node must be able to learn that it has been superseded. And this is the sentence that changes how a panel reads you, offered unprompted: "For failover I'd want fencing, because the failure I'm worried about is the old leader coming back and overwriting." Failure modes are being graded explicitly this year. Almost nobody brings this one up on their own. Full write-up, with the split-brain and zombie leader timelines diagrammed: → https://fm.dev/4qmg4wO
-
"The database is struggling." "I'd add a read replica." That exchange happens in thousands of interviews a week, and the follow-up question is always the same one candidates haven't prepared for: what, specifically, is growing? Reads, writes, data size, and geographic reach are four different problems with four different answers. Prescribing before diagnosing is the mistake. Because here's the thing about replicas: in a standard single-leader setup, they add exactly zero write capacity. Every write still funnels through one leader. Ten followers, one leader, same write ceiling. If writes are what's growing, replicas buy you nothing. Two more things worth knowing before you reach for them: Replicas don't mean fresh. Freshness comes from explicit guarantees — quorum commit plus a read-consistency policy. The existence of follower copies guarantees nothing on its own. Sync vs. async isn't a performance question, it's a choice of failure mode. Async means you can lose writes on failover. Sync means higher write latency and reduced availability. There is no third option where you get both. Naming which one you're choosing is the answer. The senior move is refusing to pick one policy for the whole system. Payment status needs leader reads for strong freshness. A social feed serves stale follower reads happily. Same cluster, different read policy per subsystem. And before any of it: indexes and caching. Exhausting the boring fixes first reads as experience, not avoidance. The full walkthrough — including how to answer the "what's actually growing" follow-up: → https://fm.dev/4zrAj0f
-
Holding a million WebSocket connections is a solved, boring problem. That's not the hard part of real-time systems anymore — and treating it as if it were is a tell in an interview. The hard part: delivering one message to a user whose socket happens to live on a different server than yours. "Add a message broker" is where most answers stop. It's actually just the start. Here's the piece people skip: Redis Pub/Sub will happily broadcast your message to every server. It will not tell you which server holds user 4,821's connection. You still need a separate presence registry — a stateful component, with its own consistency and failure questions, mapping users and rooms to servers. A few things that separate a senior answer from a mid-level one: Kafka is a poor fit here. It's built for a manageable number of topics and partitions, not millions of individual routing targets. Redis Pub/Sub is at-most-once and non-durable. If you need delivery guarantees or reconnect replay, you're pairing it with Streams, JetStream, RabbitMQ, or a database. Colocating room members on one server kills the fanout problem locally — but creates hot-room risk and a large blast radius when that server dies. The candidates who separate themselves aren't the ones who name a broker fastest. They're the ones who ask "which server holds this user" before anyone else in the room does. We wrote up the full three-stage progression — from one server to colocation to broker-plus-registry. → https://fm.dev/3TLgVLq
-
WebSockets are the most efficient option per message. They're also frequently the wrong answer — and knowing why is exactly the judgment interviewers are scoring for this year. Here's the trap: WebSockets win the metric everyone measures. Then the bill arrives for the metric nobody measured. An idle WebSocket isn't free. It holds memory and a file descriptor on your server whether or not a single byte is flowing through it. Multiply that by 100,000 mostly-idle clients, and polling — the "worse" option — can win on total cost. So where's the crossover? It comes down to message frequency times client count: Infrequent updates, huge audience → polling wins. One-way updates, near-real-time → SSE is the underused middle. Built-in reconnect, runs over standard HTTP, no new load balancer. High-frequency, bidirectional → WebSockets earn their keep — but budget for sticky routing, WebSocket-aware load balancers, and backpressure handling. That's not free either. The strongest answer in an interview isn't "WebSockets are real-time so they win." It's naming the trade explicit, in two sentences. We wrote out the full crossover math — where each option wins and why. → https://fm.dev/3SmgYwK
-
Servers can't push data to your browser. Not "usually don't." Can't. NAT and firewalls mean your server has no routable address to dial. There's no door on your laptop for it to knock on. So every technique you've heard called "server push" — polling, long polling, SSE, WebSockets — is actually the client knocking first. The server just decides how long to hold the door open once it does. Polling: client knocks repeatedly, server answers immediately each time. Long polling: client knocks once, server holds the door until there's news. SSE: client knocks once, server keeps a one-way stream open. WebSockets: client knocks once, both sides talk over the same connection. Once you see it this way, "which one is best" stops being the question. The real question is how long you need the door open, and what it costs you to keep it that way. That's the question we answer in the next two posts this week — starting with what an idle connection actually costs at scale. → https://fm.dev/4i1B8GI
-
"Just add more servers" is the most common answer in a system design interview. It's also the one that most reliably ends the conversation early. Here's the follow-up that decides the round: what did you just break? Vertical scaling is underrated. One machine, no routing layer, no shared-state problem. It's often the right early move. The real reason to leave isn't the capacity ceiling — it's the single point of failure. That's usually what forces the move, not running out of headroom. But horizontal scaling isn't a solution you apply. It's a trade you make. You exchange a capacity ceiling for a set of distributed-systems problems you now own. Three problems arrive the moment you add server number two: A routing layer you now have to operate, size, and fail over. Shared session state, which has to go somewhere. "Sticky sessions" is a decision, not a default. Downstream capacity — your database did not scale out just because your API tier did. Here's the trap most people miss: a sensible connection pool size per instance, multiplied by N instances, can exhaust your database faster than scaling was supposed to help. Do the arithmetic before you draw the third box. Naming these trade-offs unprompted is the difference between a mid-level and a senior answer. Full breakdown, including the connection pool math → https://fm.dev/4q6YtZP
-
"Design Spotify" arrives with play, pause, shuffle, and volume already attached. None of those need a backend. They're local media player functions, handled entirely client-side. And candidates routinely spend five minutes architecting them anyway. That's five minutes gone, in a round where staff-scope prompts are now landing on senior candidates. When the prompt is bigger than the time available, the round turns on what you choose not to design. Here's the filter: for each stated feature, does it require a network call, shared state, or durability? If not, it's a product feature, not an architecture problem. Say so, and move on. The features that look small and aren't: playlist collaboration, offline sync, play history. Each one hides a real distributed systems problem behind a simple verb. Then there's the scale question — do you even need to estimate it here? Genuine debate. When numbers change your design, derive them. When they don't, say why you're skipping them. Both are defensible. Silence isn't. And ask who the user actually is. A system serving anonymous casual listeners, power users, and celebrity accounts with millions of followers has three different load profiles. Naming that up front shapes everything you build after. Requirements gathering isn't a formality before the "real" design work starts. It's a distinct, learnable skill — and it's being scored as one. Side-by-side of a strong opening vs. a weak one on this exact prompt → https://fm.dev/4waT6dg
-
Three things changed about the system design interview this year. Only one of them is about AI. One: the bar rose because supply did. More candidates are clearing the old bar than there are roles to fill. So interviewers started separating people on cost, failure modes, and operational judgment — the things that used to be extra credit. Two: scope moved down a level. System design rounds are appearing in mid-level loops. Senior candidates are getting staff-scope questions. Three: LLM infrastructure entered the general pool. "Design a system that serves an LLM" was an ML-role question twelve months ago. It now shows up in general software loops. What this means in practice is smaller than it sounds. Naming a queue is table stakes. Saying what happens when the queue backs up is the signal. Drawing three app servers is table stakes. Saying what your connection pool does when you multiply it by three is the signal. The systems did not get more complicated. The scoring did. We wrote up the full breakdown — the three new scoring axes, and what a weak and strong answer sounds like on each. → Blog link https://fm.dev/4geq0oz