Read real config and code from the four cases, then pick the senior fix: a retry loop missing dedup, an offset-paginated feed, a chat handler that acks before persisting, and an autocomplete that ranks on the read path.
SDCSenior◷ 14 min
Level
FoundationsJuniorMiddleSenior
The bugs in social systems hide in a few lines: a retry without a dedup check, an OFFSET in a feed query, an ack before a write, a database call on the read path. Read each snippet, trace what happens at scale, and pick the change a senior engineer would commit.
Practise the design-review loop: locate the load-bearing line, reason about what it does under millions of events, and choose the fix that addresses the failure mode rather than papering over it.
Snippet 1 — the notification retry loop
def deliver(msg): for attempt in range(5): try: provider.send(msg) # may time out / 429 return except Retryable: sleep(2 ** attempt) # exponential backoff dead_letter.put(msg)
Quiz
Completed
This has bounded retries, backoff, and a DLQ. After a provider outage recovers, users still get duplicate pushes. What is missing?
Heads-up Jitter spreads retries to avoid a thundering herd on the recovered provider — worth adding — but it does not stop duplicates. A retried or re-flushed message still sends twice without an idempotency check before send.
Heads-up More retries cause MORE duplicates, not fewer. The duplicate comes from re-sending without a dedup guard; the cap controls when you give up to the DLQ, not whether a resend duplicates.
Heads-up Retrying permanent errors (invalid token, hard bounce) wastes sends and trips abuse limits — you should retry FEWER error types, not more. And it still does not address duplicates, which need an idempotency key.
Snippet 2 — the feed query
SELECT post_id FROM feedWHERE user_id = :uidORDER BY created_at DESCLIMIT 20 OFFSET :n; -- n grows as the user scrolls
Quiz
Completed
Deep scrolls get slow and users report posts appearing twice or being skipped. What is the cause and fix?
Heads-up An index helps the sort but OFFSET n still scans and throws away n rows, so it slows with depth regardless, and it still shifts under concurrent inserts. The structural fix is a cursor, not an index.
Heads-up The underlying list shifts when posts are inserted, so cached pages still skip/dupe, and a personalized feed churns the cache. Cursor pagination fixes both cost and correctness.
Heads-up OFFSET n still skips n rows however you slice it, and deep scroll accumulates a large n; it still skips/dupes under inserts. Anchor on the last seen item with a cursor.
What is the correctness bug, and how should the handler be reordered?
Heads-up The 'sent' tick means the server durably has the message, not that the recipient received it (that is the separate 'delivered' tick). Persist-then-ack is the fix; coupling ack to recipient delivery conflates two different ticks.
Heads-up That speed is exactly the trap: if the process crashes after the optimistic ack and before persist, the message is silently lost — the one thing chat must never do. Persist before you ack.
Heads-up The bug is ordering, not concurrency: ack happens before the durable write. Persisting first fixes it; a lock does not address the lost-message-on-crash window between ack and persist.
Snippet 4 — the autocomplete endpoint
def suggest(prefix): rows = db.query( "SELECT q FROM search_log WHERE q LIKE %s GROUP BY q ORDER BY count(*) DESC LIMIT 10", prefix + "%") # runs on every keystroke return [r.q for r in rows]
Quiz
Completed
This ranks matching queries live on every keystroke. It is 600 ms slow and overloads the cluster. What is the fix?
Heads-up An index speeds the LIKE scan but you still run a GROUP BY / ORDER BY aggregation per keystroke over a huge log — too slow for tens of ms, hundreds of thousands of times a second. The ranking must not be on the read path at all.
Heads-up A naive per-prefix cache still misses constantly on the long tail and is stale without a refresh pipeline; the structured answer is a precomputed trie with top-k per node, refreshed by an offline build.
Heads-up A smaller window is still a live aggregation on every keystroke — faster, but still on the read path and still a keystroke storm. Precompute the ranking offline and serve an O(1) trie lookup.
Recall before you leave
01
Why does a retry loop with backoff and a DLQ still duplicate, and why is OFFSET wrong for a feed?
02
Why must a chat handler persist before it acks, and why can't autocomplete rank on the read path?
Recap
The four snippets each hide a one-line failure. The notification retry loop has backoff and a DLQ but no idempotency key, so a re-flushed backlog duplicates — retries are at-least-once, dedup must guard the send. The feed query uses OFFSET, which scans n rows (slow at depth) and shifts under inserts (skips/dupes); the fix is cursor pagination. The chat handler acks before persisting, so a crash loses the message under a confident tick; persist (and assign the sequence) first, then ack. The autocomplete endpoint ranks live per keystroke over the log, which cannot meet the budget or the load; precompute top-k per trie node offline and serve an O(1) read. The senior habit: find the load-bearing line, reason about it at scale, and apply the fix the failure mode demands.