About SONiC Home SONiC Guide Key Concepts System Design Q&A Hello Interview Interview Examples SONiC vs Ceph Repo README
Page 3 of 6 · Interview practice

System Design Interview: Q&A

Questions and answers for the important topics in System Design Interview Vol. 1 and Vol. 2 by Alex Xu. Answer each question aloud before you open it. Where SONiC does the same thing in practice, a SONiC example ties the concept back to the SONiC Guide and the repo's design docs.

Cheat sheet: recurring building blocks

Building blockUse it forSONiC example
Load balancer / hashingSpreading load, stateless tiersECMP hashing across next hops
CacheRead-heavy hot dataRedis holding all switch state in memory
Message queue / pub-subDecoupling, absorbing burstsProducer/ConsumerStateTable, ZMQ channel
ShardingWrite scale, data sizePer-ASIC namespaces, multiple Redis instances
Replication / failoverAvailabilityDual ToR, MCLAG, SmartSwitch HA pairs
Rate limitingProtecting shared resourcesCoPP policers, syslog rate limit
IdempotencySafe retriesIdempotent SAI API during warm restart
MonitoringKnowing when things breakFlex counters, CRM, gNMI telemetry

Volume 1

Foundations plus 12 classic design problems.

Scale from zero to millions of users

Start with one server, then add a database tier, load balancers, replication, caches, a CDN, stateless web tiers, multiple data centers, queues and sharding.

Why make the web tier stateless, and where does the state go?

A stateless tier lets any server handle any request, so you can autoscale and replace servers freely. Session state moves to a shared store (Redis, a NoSQL database), and the load balancer no longer needs sticky sessions.

SONiC example: daemons such as orchagent keep little durable state of their own. The truth lives in Redis (CONFIG_DB/APPL_DB), so a crashed container can restart and rebuild its state from the database.
Vertical vs horizontal scaling: what are the trade-offs?

Vertical scaling (a bigger machine) is simple but has a hard ceiling and leaves a single point of failure. Horizontal scaling (more machines) has no fixed ceiling and adds redundancy, but it needs load balancing, data partitioning, and handling of consistency.

SONiC example: multi-ASIC and chassis systems scale out by running one SONiC stack per ASIC or line card rather than one ever-larger process.
What does database replication buy you, and what are the pitfalls?

Primary/replica replication gives read scaling and failover. The pitfalls are replication lag (stale reads), promoting a replica that is behind, and split-brain when two nodes both think they are primary.

When do you add a cache, and what are its failure modes?

Add a cache when the workload is read-heavy and some staleness is acceptable. Choose an expiration policy and an eviction policy (LRU/LFU). Watch for consistency between the cache and the database, a single point of failure, and a thundering herd on cold start or when a hot key expires.

Why add a message queue between tiers?

A queue decouples producers from consumers, absorbs traffic spikes, and lets each side scale and fail independently.

SONiC example: fpmsyncd (producer) and orchagent (consumer) meet at APPL_DB. A burst of a million BGP routes queues up there instead of overwhelming the hardware writer. See BGP loading optimization.

Back-of-the-envelope estimation

Powers of two, latency numbers, availability "nines", QPS and storage math.

Estimate QPS and storage for 300M monthly users, 50% daily active, 2 posts per day, 10% with media.

DAU = 150M. Write QPS ≈ 150M × 2 / 86,400 ≈ 3,500. Peak ≈ 2× ≈ 7,000. Media storage ≈ 150M × 2 × 10% × 1 MB ≈ 30 TB/day, or about 55 PB over 5 years.

How much downtime per year is 99.9% vs 99.99% availability?

99.9% allows about 8.77 hours per year. 99.99% allows about 52.6 minutes. 99.999% allows about 5.26 minutes. Every added nine costs a lot more redundancy and automation.

SONiC example: warm reboot exists because a planned upgrade with a cold reboot (minutes of outage) on every switch would use up an availability budget quickly.
Which latency numbers should you know by heart?
  • L1 cache ≈ 0.5 ns · main memory ≈ 100 ns
  • Compress 1 KB ≈ 10 µs · round trip in one data center ≈ 500 µs
  • Disk seek ≈ 10 ms · round trip from CA to Netherlands ≈ 150 ms

The lessons: memory is fast and disk is slow, avoid disk seeks, compress before sending, and cross-region calls are expensive.

How would you estimate the time to program 1M routes into a switch?

Break the pipeline into stages (BGP → FPM → Redis → orchagent → syncd → SAI). Estimate the per-route cost of each stage, or its batch throughput. The slowest stage sets the total time. If SAI can take about 50k routes/s, 1M routes need at least 20 s. Then check whether the other stages keep up and whether they overlap.

SONiC example: route-performance and CRM are real capacity-planning docs.

A framework for system design interviews

Four steps: understand and scope (3–10 min), high-level design (10–15), deep dive (10–25), wrap up (3–5).

What should you clarify in step 1?

Features in scope, number of users (DAU), read/write ratio, latency and consistency needs, data size and growth, existing tech constraints, and non-functional requirements (availability, security, cost). Write your assumptions down.

What are common red flags in a design interview?

Jumping into a solution without clarifying the problem. Over-engineering. Going deep on one component too early. Ignoring trade-offs. Designing silently without talking to the interviewer.

What belongs in the wrap-up?

Bottlenecks, failure scenarios, how to handle the next 10× of scale, monitoring and alerting, the rollout and upgrade strategy, and anything you would redesign given more time.

SONiC example: SONiC HLDs end with "open issues" or "future work" sections. Reading them is good practice for wrap-ups. See warm-reboot open issues.

Design a rate limiter

Algorithms, where to place the limiter, distributed counters, and rules plus headers.

Compare the five rate-limiting algorithms.
  • Token bucket: tokens refill at rate r up to capacity b. Allows bursts. Simple and memory-efficient.
  • Leaking bucket: a FIFO queue drained at a fixed rate. Produces smooth output, but bursts fill the queue.
  • Fixed window counter: simple, but allows up to 2× the limit around window edges.
  • Sliding window log: exact, but stores a timestamp for every request.
  • Sliding window counter: weighted mix of the current and previous windows. A good approximation at low cost.
SONiC example: CoPP uses hardware policers per trap group, a token-bucket-style CIR/CBS configuration, to protect the switch CPU. Storm control and syslog rate limiting apply the same idea.
Where should the rate limiter live?

Client-side limits are unreliable. Server-side limits work, and so does middleware such as an API gateway. Put the limiter as close to the protected resource as the architecture allows, and as early as possible so that rejected work costs little.

SONiC example: CoPP runs in the ASIC before packets reach the CPU. That is the cheapest place to drop them.
How do you rate-limit correctly across many servers?

Keep the counters in a central store such as Redis, and use atomic operations (a Lua script or INCR + EXPIRE) to avoid race conditions. For multiple data centers, use eventual sync or local limits with a global budget. Return HTTP 429 with X-Ratelimit-Remaining, -Limit and -Retry-After headers.

Hard vs soft limits, and what happens to rejected requests?

A hard limit never exceeds the threshold. A soft limit may exceed it briefly. Rejected requests can be dropped, or queued for later processing, for example orders that should not be lost.

Design consistent hashing

The rehashing problem, the hash ring, virtual nodes, and minimal key movement.

Why does hash(key) % N break when servers change?

When N changes, almost every key maps to a different server. That causes a storm of cache misses or data migration. Consistent hashing moves only about K/N keys.

How does the hash ring work, and why use virtual nodes?

Servers and keys are hashed onto a ring. Each key goes to the first server clockwise from it. With only a few real servers the partitions are uneven, so each server is placed at many points (virtual nodes). This evens out the load and also allows weighting servers by capacity.

Give a real-world example of "minimize disruption when membership changes."

Network ECMP. A switch hashes each flow's 5-tuple to pick a next hop. With naive modulo hashing, losing one next hop reshuffles most flows and breaks TCP connections through stateful devices.

SONiC example: Fine-grained next-hop groups use a fixed bucket table. Only the failed member's buckets are reassigned, so other flows stay put, just as with consistent hashing. See also consistent ECMP for VXLAN.

Design a key-value store

CAP, partitioning, replication, quorum, versioning, failure detection, and the read/write paths.

Explain the CAP theorem and give CP and AP examples.

When there is a network partition, a system must choose either Consistency (reject or block writes) or Availability (accept writes that may conflict). CP systems include a bank ledger or HBase. AP systems include Dynamo and Cassandra, which suit shopping carts. When there is no partition, the trade-off is latency vs consistency (PACELC).

How do N, W and R quorum settings trade off consistency and latency?

N is the number of replicas, W the write acks required, and R the read responses required. If W + R > N, a read overlaps the latest write, which gives strong consistency. W=1, R=N gives fast writes. W=N, R=1 gives fast reads. N=3, W=R=2 is the classic balance.

How are conflicting writes detected and resolved?

With vector clocks ([server, version] pairs). If neither clock is an ancestor of the other, the writes conflict, and the client or the application reconciles them. The simpler alternative is last-write-wins, which can lose data.

How does the system handle failures?
  • Detection: the gossip protocol (heartbeat counters).
  • Temporary failures: a sloppy quorum plus hinted handoff.
  • Permanent failures: anti-entropy with Merkle trees to find the replicas that differ.
  • Data-center outages: replicate across data centers.
Describe the write and read paths of an LSM-based store.

Write: append to the commit log, write to the memtable, and flush to an SSTable when the memtable is full. Read: check the memtable, then consult Bloom filters to skip SSTables that cannot contain the key, then read the SSTable. Background compaction merges SSTables.

How does SONiC use a key-value store as its backbone, and how does it scale it?
SONiC example: all switch state lives in Redis, in logical databases separated by role (CONFIG/APPL/ASIC/STATE/COUNTERS). Under heavy route churn a single Redis instance became a hot spot. The fix was to partition the logical databases across several Redis instances (reported 20–30% faster) and, on multi-ASIC systems, one instance per ASIC namespace. This is functional sharding plus physical sharding.

Design a unique ID generator

Multi-master auto-increment, UUID, ticket server, and Twitter Snowflake.

Compare the four approaches.
  • Multi-master (increment by k): hard to add servers, and IDs are not time-ordered across servers.
  • UUID: no coordination, but 128 bits, not numeric, and not sortable.
  • Ticket server: simple numeric IDs, but a single point of failure.
  • Snowflake: 64 bits, time-sortable, no coordination at runtime.
What is the Snowflake bit layout?

1 sign bit · 41 bits of millisecond timestamp since a custom epoch (about 69 years) · 5 bits data center · 5 bits machine · 12 bits sequence (4,096 IDs per ms per machine). Watch out for clock skew (use NTP, and refuse to issue IDs if the clock moves backwards).

Why might you need a stable "virtual" ID on top of a system-assigned ID?

When the underlying system reassigns IDs, for example after a restart, other components that store the old IDs would break.

SONiC example: sairedis hands orchagent virtual object IDs (VIDs). syncd maps them to real hardware IDs (RIDs). After warm reboot the RIDs may change, but the VIDs stay valid, so the software above never notices. See view_switch.md.

Design a URL shortener

API, 301 vs 302, hash-plus-collision vs base-62 conversion, caching, and the read-heavy workload.

301 or 302 redirect?

A 301 (permanent) is cached by browsers, which reduces server load. A 302 (temporary) sends every click to your server, which enables click analytics.

How long must the short code be?

With base 62, 62⁷ ≈ 3.5 trillion codes. That covers 365 billion URLs (100M per day for 10 years), so 7 characters is enough.

Hash + collision resolution vs base-62 of a unique ID: which is better?

Hashing (a CRC/MD5 prefix) needs collision checks, which a Bloom filter can speed up. Base-62 of a unique ID (from a Snowflake-style generator) never collides and is simple, but its codes are predictable and it needs an ID service.

How do you scale the redirect path?

Cache short → long mappings, since reads far outnumber writes. Keep the web servers stateless behind a load balancer, add database read replicas, and rate-limit abusive clients.

Design a web crawler

BFS, the URL frontier, politeness and priority, deduplication, robustness, and extensibility.

What does the URL frontier do?

It holds the URLs still to be downloaded and enforces two things. Priority: front queues ordered by page importance. Politeness: back queues, one per host, each drained by a single worker with delays, so no site is hammered. Most of the frontier lives on disk, with in-memory buffers.

How do you avoid duplicate URLs and duplicate content?

Keep a "URL seen?" check (a Bloom filter or hash set), and hash page content (a checksum or simhash) to detect duplicate or near-duplicate pages. Also guard against spider traps with a maximum URL length and depth.

How do you make the crawler robust and extensible?

Use consistent hashing to spread load across downloaders, save crawl state for recovery, and handle exceptions gracefully. Content processing uses a plug-in module chain, so a new content type (images, PDFs) is just another module.

SONiC example: the same "add features as plug-ins" idea appears in the SONiC Application Extension framework.

Design a notification system

Push (APNs/FCM), SMS, email, queues per channel, reliability, deduplication, templates, and settings.

Sketch the high-level design.

Services call a notification API, which authenticates, rate-limits and validates the request. Notifications are fetched and rendered from templates and user settings, then placed on one queue per channel (iOS, Android, SMS, email). Workers call the third-party providers. Keeping a queue per channel means an outage at one provider does not block the others.

How do you avoid losing notifications or sending duplicates?

Persist a notification log and retry with backoff. Delivery is at-least-once, so deduplicate on the consumer side using an event ID. True exactly-once delivery is not achievable.

How is a notification system like structured event publishing on a device?
SONiC example: the event-alarm framework replaces log scraping with events that follow a YANG schema. The events producer publishes them to subscribers such as gNMI collectors. The notification queue policy decides what happens when consumers fall behind: a bounded queue plus a drop or merge policy.

Design a news feed system

Feed publishing, fanout on write vs read, the hybrid approach, and caching layers.

Fanout on write vs fanout on read?

On write (push): the feed is precomputed, so reads are fast. But it is wasteful for inactive users and very expensive for celebrities with millions of followers. On read (pull): no wasted work, but reads are slow. Hybrid: push for most users, and pull celebrity posts at read time.

What do you cache?

A news-feed cache holding only post IDs, a content cache for hot posts, social-graph caches, action caches (likes) and counters. Store IDs in the feed and hydrate them on read.

How is "coalescing repeated updates" relevant to feed-like pipelines?
SONiC example: if a route flaps 5 times before orchagent reads it, ConsumerStateTable only sees the latest value, because the pending-key set holds each key once. Fanout pipelines can use the same trick to avoid wasted downstream work. See the SONiC Guide: databases.

Design a chat system

Polling vs long polling vs WebSocket, stateful chat servers, message storage, ordering, and presence.

Which transport should you use for real-time messages?

Polling is simple but wasteful. Long polling still needs reconnections and makes it hard to detect disconnects. WebSocket gives a bidirectional, persistent connection and is the usual choice. Plain HTTP is fine for non-real-time APIs such as login and profile.

What storage suits chat history, and how do you order messages?

A wide-column key-value store (HBase, Cassandra) handles very high write volume and range reads of recent messages. Order messages by an ID that is unique and sortable within the channel, either from a Snowflake-style generator or a local sequence.

How do you implement online presence?

Clients send periodic heartbeats. If no heartbeat arrives within a timeout, the user is marked offline, which avoids flapping on short disconnects. Status changes are fanned out to friends through pub/sub channels.

SONiC example: liveness detection in networks works the same way. BFD sends rapid heartbeats between peers, and a missed-detection multiplier declares a peer down. See DPU liveness via BFD.
How are long-lived subscription connections handled for streaming data?
SONiC example: the gNMI telemetry server keeps persistent gRPC streams to collectors (dial-in), or opens them itself (dial-out). The design issues are like a chat server's: connection state, reconnects, and back-pressure. See gRPC telemetry and dial-out.

Design a search autocomplete system

Trie with cached top-k results, a data-gathering service, and a query service.

How do you make top-k lookups fast in a trie?

Limit the prefix length, and cache the top-k queries at every node. A lookup then takes O(1) time after walking the prefix. This trades memory for latency.

Should you update the trie in real time?

Usually not. Aggregate the query logs offline (for example weekly), rebuild the trie with workers, and swap it in. Serve it from a trie cache backed by a key-value store. Filter harmful suggestions at the serving layer.

How do you scale the trie storage?

Shard by prefix. Because letters are unevenly distributed, use a shard-map manager that splits or merges ranges according to historical load, rather than a naive a–m / n–z split.

Design YouTube

Upload to blob storage, a transcoding DAG, CDN streaming, and cost optimization.

Walk through the upload flow.

The client gets a pre-signed URL and uploads directly to blob storage. Transcoding servers run a DAG of tasks (inspect, encode to several resolutions and codecs, thumbnails, watermark). The outputs go to storage and the CDN. The metadata is updated, and a completion event notifies the client. Metadata updates can run in parallel with the upload.

How do you speed up and harden uploads?

Split uploads into GOP-aligned chunks and make them resumable. Place upload centers close to users, and use message queues between stages so they are loosely coupled and can run in parallel.

How do you control CDN cost?

Serve only popular videos from the CDN, and serve the long tail from your own servers. Encode unpopular videos on demand, and keep region-specific content in that region.

Design Google Drive

Block servers, delta sync, deduplication, strong consistency for metadata, and conflict resolution.

Why split files into blocks?

Delta sync then uploads only changed blocks. Blocks can be compressed and encrypted, and identical blocks are deduplicated by their hash. This saves a lot of bandwidth and storage.

How are sync conflicts resolved?

The first version to be processed wins. The later one is kept as a conflicting copy for the user to merge. Metadata must be strongly consistent, so it goes in a relational database with ACID transactions.

How do clients learn about changes?

A notification service using long polling. When a client is told something changed, it pulls the new metadata and blocks.

How is "diff and apply" used for configuration, and what about rollback?
SONiC example: Generic Config Update takes a JSON Patch (the delta), validates it against YANG, orders the operations by dependency, and applies them. Checkpoints are saved versions. Rollback computes the patch between the current config and a checkpoint, the same delta-sync idea applied to config.

Volume 2

Deeper problems: geospatial, streaming, storage, and financial systems.

Proximity service

Find nearby businesses: geohash, quadtree, Google S2.

Compare geohash and quadtree.

Geohash encodes lat/long into a base-32 string. A shared prefix means the points are close, and it is easy to store in any key-value store or database index. Edge cases: points near a cell border can have different prefixes, so also search the 8 neighbouring cells. Quadtree is an in-memory tree that subdivides until each leaf has at most K businesses. It adapts to density but must be rebuilt or updated, which makes it harder to operate. S2 maps the sphere onto a Hilbert curve and supports arbitrary regions (geofences).

How do you scale reads?

Business data changes rarely, so it is read-heavy. Use read replicas and a cache keyed by geohash, holding a list of business IDs, plus a business-info cache. Deploy in multiple regions to reduce latency.

Nearby friends

Real-time location updates over WebSocket with Redis pub/sub fanout.

How do location updates flow?

The client sends its location over WebSocket every ~30 s. The server updates a location cache (with a TTL), appends to a history database, and publishes to the user's own Redis pub/sub channel. Friends' WebSocket handlers subscribe to that channel, compute the distance, and push updates to clients within range.

How do you scale Redis pub/sub?

Shard the channels across a Redis cluster using consistent hashing, with service discovery (for example ZooKeeper or etcd) holding the hash ring. The work is CPU-bound, so size the cluster by CPU. When adding nodes, resubscribe carefully to avoid a storm of resubscriptions.

SONiC example: SONiC relies heavily on Redis pub/sub and keyspace notifications for daemons to learn about changes. It is a single-node example of pub/sub fanout. See the SONiC Guide.

Google Maps

Map tiles, routing tiles, hierarchical graphs, ETA, and navigation updates.

How is the map rendered efficiently?

Precompute tiles at each zoom level and serve them as static images or vectors from a CDN. The client calculates which tile URLs it needs from its position and zoom level, and servers take no part in that.

How is routing made tractable on a planet-sized graph?

Split the road graph into routing tiles at several levels of detail (local streets, arterials, highways). A* or Dijkstra runs over the tiles loaded on demand. ETAs combine the route with live and historical traffic, often through ML models.

How does routing in a switch compare?
SONiC example: BGP computes paths hop by hop instead of globally, and the switch holds only next hops per prefix. The RIB/FIB split (control plane vs forwarding table) resembles "compute routes" vs "serve tiles." See SONiC FIB LLD.

Distributed message queue

Topics, partitions, consumer groups, a write-ahead log, replication with ISR, and delivery semantics.

Why store messages as an append-only log on disk?

Sequential disk I/O is fast, especially with the OS page cache and batching. Segment files are easy to retain and delete. Consumers track their own offsets, so re-reading is cheap and the broker stays simple.

How do partitions and consumer groups work?

A topic is split into partitions, and ordering is guaranteed only within a partition, so choose the key carefully. Within a consumer group, each partition is consumed by one consumer. Parallelism is therefore capped by the partition count, and a coordinator rebalances partitions when consumers join or leave.

What do acks=0/1/all mean, and how do you get each delivery semantic?

acks=0 is fastest, and messages may be lost. acks=1 waits for the leader only. acks=all waits for every in-sync replica (ISR) and is the most durable. At-most-once: commit the offset before processing. At-least-once: commit after processing. Exactly-once: at-least-once plus idempotent processing or transactions.

What trade-offs appear in a producer-side batching pipeline?

Larger batches and fewer flushes give higher throughput but add latency and memory use. Small batches give low latency but more per-message overhead.

SONiC example: in the BGP loading HLD, the bottlenecks turned out to be "fpmsyncd flushes the pipeline too frequently" and "pipeline size is too small." That is exactly this trade-off. The ZMQ producer/consumer design is a lighter message queue for hot paths, with async sends and retries when the queue is full or the connection breaks.

Metrics monitoring & alerting

Collection (pull vs push), a time-series database, a Kafka buffer, downsampling, and alert rules.

Pull vs push collection?

Pull (Prometheus): easy health detection because a failed scrape means the target is down, but it needs service discovery and has trouble with short-lived jobs and firewalls. Push (agents): works across firewalls and for short jobs, but it is harder to tell a dead host from a quiet one. Many systems use both.

SONiC example: SONiC does both. SNMP and gNMI Get are pull. gNMI Subscribe, dial-out and high-frequency telemetry are push.
Why a time-series database, and how do you control storage?

The workload is write-heavy, with time-range and aggregation queries over labelled series. TSDBs optimize this with columnar compression (delta-of-delta, Gorilla). Keep storage under control with downsampling (raw data for 7 days, 1-minute data for 30 days, 1-hour data for a year) and cold storage.

How is collection done on the device side?
SONiC example: Flex counters: orchagent registers which counters to poll, and at what interval, in FLEX_COUNTER_DB. syncd polls the ASIC in bulk and writes COUNTERS_DB. CRM applies threshold alerting with high and low watermarks, which avoids flapping alerts. System health aggregates everything into an overall status.

Ad click event aggregation

Streaming aggregation, windows, watermarks, exactly-once, and reconciliation.

Lambda vs Kappa architecture?

Lambda runs a batch path and a streaming path in parallel, which means two codebases. Kappa uses only the streaming path and handles reprocessing by replaying the log. Kappa is simpler if the stream engine is reliable enough.

Event time vs processing time, and what are watermarks?

Event time is when the click actually happened. It is accurate, but events arrive late. A watermark extends a window to wait for stragglers: longer is more accurate but adds latency. Events that arrive after the watermark are corrected by end-of-day reconciliation.

How do you get exactly-once aggregation?

Store the consumer offset together with the aggregation result, atomically or in a distributed transaction. Deduplicate events by ID. Use a batch reconciliation job as a final check.

Window types?

Tumbling windows are fixed and do not overlap (clicks per minute). Sliding or hopping windows overlap (top ads in the last M minutes). Session windows are separated by gaps in activity.

Hotel reservation system

Concurrency control, idempotency, overbooking, and data partitioning.

How do you prevent double booking?

There are three options. Pessimistic locking (SELECT … FOR UPDATE) is safe but has contention and deadlock risk. Optimistic locking uses a version column and retries on conflict, which is good when contention is low. Database constraints (CHECK total_reserved ≤ total_inventory) are simple but hard to apply across tables.

How do you stop a user from booking twice when they click "Submit" twice?

Generate an idempotency key (the reservation ID) when the booking page loads, and put a unique constraint on it. A duplicate submission fails the constraint and returns the original result.

SONiC example: idempotency is essential during warm restart. Replaying state into syncd must not create duplicate hardware objects, so the SAI Redis API is made idempotent.
How do you scale it?

Shard by hotel_id, cache inventory with the database as the source of truth, and split into microservices. When a transaction must span services, use a saga or 2PC, or keep those tables in one database.

Distributed email service

SMTP/IMAP, metadata storage, search, attachments, and deliverability.

What storage suits email metadata?

The access pattern is per-user, mostly recent, with read/unread filtering. A wide-column store partitioned by user_id, with denormalized tables (for example read and unread as separate tables) to avoid costly filters. Attachments go in object storage.

How do you build search?

Use either a general search cluster (Elasticsearch, updated asynchronously through Kafka) or a custom per-user index built into the storage engine (an LSM tree optimized for heavy writes). Search is per mailbox, which makes partitioning simple.

How do you protect deliverability?

Use dedicated IPs, warm them up gradually, classify and throttle outbound mail, handle bounces and complaints quickly, and set up SPF, DKIM and DMARC.

S3-like object storage

Separate metadata from data, append-to-file data nodes, durability, and multipart upload.

Why separate the metadata store from the data store?

They have very different profiles. Metadata is small, mutable and queried. Object data is large, immutable and read by ID. Separating them lets each scale and be optimized on its own terms, much like a UNIX inode vs its data blocks.

Replication vs erasure coding?

3× replication has 200% overhead and gives simple, fast reads. Erasure coding (for example 8+4) has 50% overhead and similar or better durability, but reads and repairs cost more CPU and network. Use replication for hot data and erasure coding for cold data.

How do you store many small objects efficiently?

Append objects into large read-write files. When a file is full, seal it as read-only. Keep an object-ID → (file, offset, size) mapping in a local database such as RocksDB or SQLite. Deletes only mark objects, and compaction reclaims the space later.

How do you detect corruption and support versioning?

Store a checksum with each object or segment and verify it on read. For versioning, add a version ID (for example a TimeUUID) to the metadata, and make a delete write a delete marker.

Real-time gaming leaderboard

Redis sorted sets, scaling out, and ties.

Why Redis sorted sets instead of SQL?

A sorted set is a hash plus a skip list. ZINCRBY, ZREVRANGE for the top 10, and ZREVRANK for a user's rank all run in O(log n). Ranking with SQL means sorting large tables on every query, which does not work in real time.

How do you scale beyond one Redis node?

Either use fixed partitions by score range, which makes the top-N query easy, or hash partitions, which need a scatter-gather to find the top N. Persist scores to a database as the source of truth, and rebuild Redis from it if needed.

Payment system

Payment service, PSP integration, ledger, wallet, reconciliation, retries, and idempotency.

How do you guarantee a customer is never charged twice?

Pass an idempotency key (for example the order ID) to your own API and to the PSP. Combine at-least-once retries with idempotent processing to get effectively exactly-once behaviour.

How do you handle failures and retries?

Persist the payment state machine. Retry transient errors with exponential backoff and jitter. Send messages that keep failing to a dead-letter queue. Never retry non-retryable errors, such as an invalid card.

Why reconciliation, and what is double-entry bookkeeping?

Async systems drift, so a nightly job compares your ledger with the PSP and bank settlement files and flags mismatches. Double-entry means every transaction debits one account and credits another by the same amount, so all entries sum to zero. This makes errors detectable.

SONiC example: reconciliation is also central to SONiC. After warm restart, the new desired state is compared with what the hardware actually has, and only the differences are applied. See SWSS warm restart.

Digital wallet

Balance transfers at scale: distributed transactions, event sourcing, and CQRS.

Compare 2PC, TC/C and Saga.
  • 2PC: database-level prepare/commit. Strong consistency, but locks are held and the coordinator is a single point of failure.
  • TC/C (Try-Confirm/Cancel): application-level with compensating actions. No long locks, but more code.
  • Saga: a sequence of local transactions, each with a compensating transaction. Can be choreographed or orchestrated. Good for long-lived flows.
What is event sourcing, and why use it for a wallet?

Store an immutable log of commands and events, and derive the state (balances) by replaying them. The result is fully auditable and reproducible. Combine it with CQRS: one write model, and many read models built from the event stream. Snapshots speed up replay.

SONiC example: sairedis can record every SAI operation to a recording file (sairedis.rec) that can be replayed to reproduce issues. It is a lightweight form of event sourcing used for debugging.
How do you make the event log highly available?

Replicate it with a consensus protocol (Raft) across several nodes. This is state-machine replication: every node applies the same log in the same order and ends up in the same state.

Stock exchange

Order book, matching engine, sequencer, very low latency, and determinism.

What are the critical-path components?

The path is client gateway → risk checks → order manager → sequencer (stamps sequence IDs so there is a total order) → matching engine (price-time priority on the order book) → sequencer → market data and reporting.

How do you achieve microsecond latency?

Put everything on one server. Pin a single thread per component to a CPU core, with no context switches or locks. Communicate through shared memory (mmap) or ring buffers. Avoid garbage collection and disk on the critical path. Keep the design deterministic so that a hot-warm replica can take over by replaying the same event log.

Why is a single-threaded, deterministic core a good idea?

No locks and no contention, predictable latency, and easy replication by replay.

SONiC example: orchagent is also a single-threaded event loop that processes table changes in order. That keeps it simple and deterministic, but it can become a throughput limit at scale. Orchagent route redesign and the BGP loading HLD discuss adding async and parallel stages around it.