Irbisa · Cheatsheet
Backend System Design
Caching, queues, idempotency, rate limiting, consistency, outbox, pagination, SLAs
What is the difference between cache-aside, read-through, write-through and write-behind caching, and when would you pick each?
What is the difference between strong and eventual consistency, and what do CAP and PACELC actually tell you when you design a service?
What is consistent hashing, why does
hash(key) % Nmove almost every key when you add a node, and where is it used?// Ring maps keys to nodes. Each node sits at many points (virtual nodes),// so the load evens out and a removed node's keys spread over all the others.type Ring struct { points []uint64 // sorted positions of every virtual node owner map[uint64]string // position -> node}…How do fixed window, sliding window, token bucket and leaky bucket rate limiting differ, and which one would you choose?
Producers enqueue work faster than consumers can process it. What is backpressure, and where do you apply it in a Go service and across a queue?
// A bounded hand-off between the handler and a fixed number of workers.var jobs = make(chan Job, 1000) func enqueue(w http.ResponseWriter, r *http.Request) { job, err := decodeJob(r) if err != nil {…How do you implement keyset (cursor) pagination when the sort column is not unique, and what do you give up compared with
OFFSET?// The cursor is the sort key of the last row returned; id breaks ties between equal timestamps.type cursor struct { CreatedAt time.Time `json:"t"` ID int64 `json:"i"`}…UUIDv4, UUIDv7 or a
bigintidentity as the primary key: what does each do to a Postgres B-tree index, and when do you need Snowflake-style ids?Find the bug: two retries of the same payment arrive 5 ms apart, and both charge the card.
func (s *Service) Pay(ctx context.Context, key string, req PayRequest) ([]byte, error) { var saved []byte err := s.db.QueryRowContext(ctx, `SELECT response FROM idempotency_keys WHERE key = $1`, key).Scan(&saved) if err == nil { return saved, nil // seen this key before: replay the stored response } resp, err := s.psp.Charge(ctx, req) if err != nil { return nil, err } _, err = s.db.ExecContext(ctx, `INSERT INTO idempotency_keys (key, response) VALUES ($1, $2)`, key, resp) return resp, err}Your API calls four dependencies in sequence, each with a 99.9% SLO. What availability can you promise, and how do you promise more than they give you?
When do you put an in-process cache in front of Redis, and how do you keep 40 pods' local copies from going stale or melting on one hot key?
Users sometimes see an old price for hours after an update, although every write deletes the cache key. Which races cause it, and how do you close them?
A user saves their profile, reloads and sees the old name: reads go to an async replica. How do you give read-your-writes without sending all reads to the primary?
// After a write, hand the primary's WAL position back to the client (cookie or header).func (s *Store) Rename(ctx context.Context, userID int64, name string) (lsn string, err error) { if _, err = s.primary.ExecContext(ctx, `UPDATE profiles SET name = $2 WHERE user_id = $1`, userID, name); err != nil { return "", err }…How do you choose the timeout for a call to a dependency, and when do hedged requests help with tail latency?
// hedged calls once and, if there is no answer after delay (about the dependency's p95),// sends a second copy. The first success wins; returning cancels the other attempt.func hedged[T any](ctx context.Context, delay time.Duration, call func(context.Context) (T, error)) (T, error) { ctx, cancel := context.WithCancel(ctx) defer cancel()…Your outbox relay polls the table every 100 ms. When would you replace it with CDC such as Debezium, and what new failure modes does a replication slot bring?
A flash sale has 100 units and 50,000 buyers in the first second. How do you avoid overselling without the inventory row becoming the bottleneck?
// The check and the write are one statement, run under the row lock.func (r *Repo) Reserve(ctx context.Context, productID int64, qty int) error { res, err := r.db.ExecContext(ctx, ` UPDATE inventory SET stock = stock - $2 WHERE product_id = $1 AND stock >= $2`, productID, qty) if err != nil {…Design a ledger for money transfers between accounts: double-entry records, idempotency, balances and reconciliation.
CREATE TABLE accounts ( id bigint PRIMARY KEY, currency char(3) NOT NULL, balance bigint NOT NULL DEFAULT 0 -- minor units; a cache of SUM(entries.amount));…Design a URL shortener like bit.ly: 100 million new links a month, far more reads than writes. Walk through the API, ID generation, storage and redirects.
const alphabet = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" // encode turns a unique id into a short code: ids below 62^7 ≈ 3.5 trillion fit in 7 chars; larger ids give longer codes.func encode(id uint64) string { if id == 0 { return "0"…Design a view counter for videos that receive 50,000 increments a second: where do you count, how do you persist, and what can you lose?
// Views adds views up in memory: however many Inc calls arrive,// each pod writes each video at most once a second.type Views struct { mu sync.Mutex deltas map[int64]int64 // video id -> views since the last flush; make it before use // flush writes one batch: a Redis pipeline of INCRBY, or one Kafka message.…Design a notification service that sends email, SMS and push to millions of users. How do you handle spikes, retries, duplicates and user preferences?
Design the backend of a chat: delivery to online and offline users, ordering within a conversation, read receipts and storage.
CREATE TABLE conversations ( id bigint PRIMARY KEY, last_seq bigint NOT NULL DEFAULT 0 -- the next message gets last_seq + 1); CREATE TABLE messages (…Design a news feed: fan-out on write versus fan-out on read, and what changes for an account with ten million followers?
// Feed merges the user's precomputed feed (fan-out on write) with the recent// posts of the celebrities they follow (fan-out on read). Post ids are// time-ordered (Snowflake-style), so sorting ids sorts by time.func (s *Service) Feed(ctx context.Context, userID int64, limit int) ([]Post, error) { ids, err := s.feeds.Recent(ctx, userID, limit) // pushed in when authors posted if err != nil {…Design a rate limiter for a public API on 50 replicas, 100 requests a second per API key. Where does the state live, and what happens when Redis is slow or down?
// Token bucket per key, refilled lazily from Redis's own clock, atomic inside Redis.var tokenBucket = redis.NewScript(`local rate, burst = tonumber(ARGV[1]), tonumber(ARGV[2])local t = redis.call('TIME')local now = t[1] * 1000 + math.floor(t[2] / 1000)local s = redis.call('HMGET', KEYS[1], 'tokens', 'ts')…