Quiz
Every gotcha from every post, with the explanation hidden until you click. Read the title, say in one sentence why it is a mistake and what to do instead, then reveal to check. Mark the ones you know so you can hide them and drill the rest. Your marks are saved in this browser only.
Opening and Scoping the Problem
From the first five minutes:
- Ritual questions. Asking because the book said to, with no visible effect on the design. Every question should change something.
- Too many questions. Ten minutes of clarification is its own failure. Cap at five or six, then state defaults.
- Not cutting anything. Choose the core flow and say what you are deferring.
- Never naming the hard part. Without it, the deep dive has no target.
- Estimating everything, or nothing. Three numbers and a “so.”
- No agreement on the plan. Ten seconds prevents a minute-twenty redirect.
- Dead air. Narrate your thinking.
- Mishandling “assume whatever you want.” It is an invitation to state defaults, not skip the step.
- Not writing it down. Requirements go top-left and stay there.
- Treating the interviewer as an examiner. They are a colleague for forty-five minutes.
From back-of-envelope numbers:
- False precision. A day is 100,000 seconds. A million a day is ten a second.
- Forgetting the peak. Average is what you compute; peak is what you design for.
- Forgetting replication and indexes. Raw 1 TB is 4 TB provisioned.
- Mixing bits and bytes. A 10 gigabit link moves about 1.25 gigabytes per second.
- Estimating things that do not matter. Estimate the dimension that drives a decision.
- Not saying the assumption. An assumption can be corrected; a bare number cannot.
- Stopping at the number. Always finish with “so.”
And from the design posts:
- Skipping capacity estimation. Without numbers you cannot justify anything. (URL shortener)
- Answering in the first ten seconds. The question is a prompt to ask clarifying questions. (Monolith or microservices)
- Designing for the average. Three per second on average, a hundred thousand in the first minute of an on-sale. (Ticket booking)
Consistency and Isolation
From what consistency actually means:
- Three different words spelled the same way. ACID consistency, CAP consistency, and consistency models are different things. Say which.
- Strong versus eventual as a binary. The session guarantees in between are what users notice.
- Choosing eventual without naming the anomaly. “Eventual” means “the user may see X, and here is why that is fine.”
- Assuming a primary with replicas is strongly consistent. Replica reads lag.
- Assuming the cache agrees with the database. Cache-aside has a window and a race.
- Confusing consistency with durability or atomicity. Different axes.
- “Kafka guarantees ordering.” Per partition only.
- Claiming linearizability across two systems. Database plus search index is never linearizable.
- Ignoring the cost. Cross-region linearizable writes have a 100 ms floor.
- Mixing up the two axes. Replication models are single-object visibility across replicas; isolation is multi-object transactions on one copy.
From transaction isolation levels:
- Assuming ACID means serializable. The I is configurable and the default almost never is.
- Assuming read committed prevents lost updates. It does not.
- Assuming “repeatable read” means the same thing everywhere. Standard, PostgreSQL, and MySQL are three different things.
- Not knowing write skew. Snapshot isolation’s one hole, and why serializable exists.
- Read-modify-write in application code. The most common production concurrency bug.
- Turning on serializable and not writing retries. Optimistic implementations abort; the application must retry.
- Checking uniqueness in code. A unique constraint is the only correct answer.
- Long transactions. Never span user think time or an external call.
- Assuming isolation spans services. It does not. That is sagas and the outbox.
- Conflating the two axes. “Two increments produced one” is isolation. “My comment vanished on refresh” is replication.
From ticket booking:
- The seat map as the source of truth. It is a display. The hold attempt decides.
- “Best available” as a serializable transaction. Pick candidates from the cache, attempt a conditional multi-row update, retry with the next candidates.
- General admission as read-modify-write. One conditional decrement.
Idempotency, Retries, and Exactly-Once
From idempotency keys, properly:
- Claiming natural idempotency that is not there. “Set status to paid” is idempotent. “Send the confirmation email” is not.
- Letting the server generate the key. A server-minted key cannot survive a lost response.
- Getting the scope wrong. Scope to caller and operation.
- Check-then-act. Reserve the key with an atomic insert first. That insert is the lock.
- Storing the key somewhere other than the side effect. Same transaction, or propagate the key downstream.
- Not storing the response. A replay must return the same status and body.
- Same key, different payload. Fingerprint the request and reject mismatches.
- Caching failures forever. A downstream timeout should be retryable under the same key; a validation error should not.
- Forgetting the second line of defense. A natural unique constraint catches what expired keys miss.
- Idempotent is not the same as ordered. Out-of-order updates are a versioning problem.
And from the design posts:
- Promising exactly-once. The scheduler cannot distinguish “ran and crashed” from “never ran.” At-least-once plus idempotent jobs. (Job scheduler)
- Check-then-increment. Two requests read 99, both pass, both write 100. The counter operation must be atomic on the store. (Rate limiter)
- Non-idempotent steps. The relay delivers at least once; every consumer needs an inbox. (Sagas and the outbox)
- Charging twice. A retry without an idempotency key on the charge is a second charge. (Ticket booking)
- The payment provider says nothing. Query by your idempotency key before deciding to retry or give up. (Ticket booking)
Coordination, Leases, and Hot Rows
From the job scheduler:
- One scheduler, or several with no coordination. Leader election, partition ownership, or an atomic claim. Name the one you are using.
- Polling without a plan. Index the due time and claim a batch atomically with
SKIP LOCKED, or use a sorted structure. - Leases that expire while the job is still running. Heartbeats extend the lease; a fencing token rejects a stale worker’s writes.
- Everything at midnight. Estimate the peak, not the average. Jitter what does not need the exact minute.
- Time zones and daylight saving. Store the zone, compute next run in it, store the result in UTC.
From ticket booking:
- Locking the seat during payment. The lock is held for the provider’s latency. The hold is a status and an expiry, not a lock.
- Holds that expire by cron. Dead inventory between expiry and the job, and a race with purchase. Expiry lives inside the conditional update.
- No waiting room. An unbounded burst, random fairness, and bots win.
- Pretending the hot event is not hot. The event is the unit of contention. Say how one shard survives it.
And from the other design posts:
- A hot tenant on one shard. Split the tenant’s counter into sub-keys. (Rate limiter)
- Hashing the URL and truncating. Collisions become likely long before the keyspace fills, and the check-and-retry is a race. (URL shortener)
- Using the database’s auto-increment ID. Predictable, enumerable, and a single write sequence. (URL shortener)
Cross-Service Transactions
From sagas and the outbox:
- The dual write. Save then publish, or publish then save. Either order loses something on a crash.
- Saying “two-phase commit” as if it were available. Brokers, APIs, and managed services do not participate; the coordinator is a single point of failure.
- Compensation as rollback. A refund is a new transaction that can fail.
- Choreography spaghetti. Fine for two or three steps; past that it needs an owner.
- The orchestrator as a god service. Keep it to sequencing and state.
- Forgetting that intermediate states are visible. Sagas have no isolation. Use a pending status as a semantic lock.
- Designing only the happy path. Every compensation needs a “what if this fails.”
- Losing ordering in the relay. Partition by aggregate ID.
And from the monolith post:
- Forgetting the data. Splitting the code is easy. A service that shares a database is not a separate service. (Monolith or microservices)
Failure Handling and Degradation
- No answer for when the counter store is unavailable. Fail open for public API limits; fail closed for limits that protect a fragile downstream. Per rule, not global. (Rate limiter)
- Making the limiter a synchronous hop to another region. Keep limits per region or accept approximate global limits. (Rate limiter)
- No misfire policy. Scheduler down twenty minutes; skip, fire once, or fire all is per-job configuration. (Job scheduler)
- No history. “Why did my job not run at 09:00?” is the first support ticket. (Job scheduler)
Read Paths, Caching, and the Client Contract
- Designing for writes when the workload is reads. A shortener sees 10 to 100 reads per write; the read path is the design. (URL shortener)
- Ignoring the 301 vs. 302 decision. A 301 caches at the browser and kills analytics. (URL shortener)
- Forgetting the unglamorous requirements. Expiration, deletion, custom aliases, rate limiting, abuse. (URL shortener)
- Answering “should the same long URL always return the same short code?” without asking who is asking. A product question disguised as a technical one. (URL shortener)
- Naming an algorithm without saying what it optimizes for. Every rate limiting algorithm trades bursts, memory, and boundary accuracy. (Rate limiter)
- Keying on IP address by default. Punishes everyone behind a NAT; barely inconveniences a botnet. (Rate limiter)
- Forgetting the response contract. 429, Retry-After, RateLimit headers. (Rate limiter)
- Confusing rate limiting with load shedding. One is a policy about a client; the other is a policy about the server. (Rate limiter)
Boundaries and Proportionality
From monolith or microservices:
- Treating “microservices” as the grown-up option. An experienced interviewer will drill into the costs until you run out.
- Ignoring the modular monolith. The most defensible starting point for most new systems.
- Confusing scaling the code with scaling the team. Microservices scale deployment independence, not throughput.
- Not mentioning Conway’s Law. Boundaries that do not match teams do not hold.
And from the other posts:
- Conflating scheduling, execution, and orchestration. When, what, and dependencies are three concerns. (Job scheduler)
- Using a saga where the boundary is wrong. If every operation needs a saga across A and B, they are one service. (Sagas and the outbox)
Coding Screens
From ten coding interview questions in Kotlin:
Array<Int>whereIntArraybelongs. The first boxes every element. Use the primitive arrays.- Mixing Kotlin’s
ArrayDequewith Java’s.addLastandremoveLastversuspushandpop. Pick one API. ==when you meant===.==isequals; for nodes and sentinels, identity matters.- Expecting a smart cast on a mutable property. Copy to a local
val, or use?.and!!deliberately. ..versusuntil.0..nincludesn. Most Kotlin off-by-one bugs are this one.- Negative modulus.
-1 % 5is-1. UseMath.floorModfor buckets and ring buffers. - Forgetting that
PriorityQueueandLinkedHashMapoverrides are Java. Thejava.utilimport, andremoveEldestEntryis Java’s API. Intoverflow in sums, midpoints, and sentinels.lo + (hi - lo) / 2;amount + 1as infinity;Longwhen bounds justify it.sortedBywhen you meantsortBy. One allocates, one sorts in place and mutates the caller’s input.- Recursion where the JVM stack cannot follow. Grid DFS and recursive list reversal overflow. Iterate.
From ten more coding interview questions in Kotlin:
Char.toInt()is deprecated.c.codeon modern Kotlin;c - 'a'works everywhere.- Sorting a string is not one call.
toCharArray().sorted()is aList<Char>; prefer a count-array key. MutableList.removeLast()collides with JDK 21. Can resolve to Java’s method and throw on older runtimes. UseremoveAt(lastIndex).- Adding the live path to the results.
result.add(ArrayList(path)), or every result is the same list. - The shared-row bug in 2D arrays.
Array(n) { IntArray(m) }, never a row captured from outside the lambda. - A local recursive lambda does not compile. Use a local
fun. PairandTriplein hot loops. One allocation and two boxes per element. Read the array directly.var sum = 0infersInt. Kadane, prefix sums, and products overflow silently.0L.- Skewed trees are linked lists. Recursion is O(height) and height can be n. Explicit stack.
- Building strings with
+in a loop. O(n²).StringBuilderorjoinToString.
From concurrency in Kotlin:
- Cancellation is cooperative, and CPU-bound loops do not cooperate by default. Check
isActive, callensureActive(), oryield(). - Catching
CancellationExceptionand swallowing it.catch (e: Exception)andrunCatchingboth do. Rethrow it, always. GlobalScopeand other unstructured launches. No parent, no waiting, no cancellation. Say what the scope is.asyncexception semantics. In a non-supervisor scope a failingasynccancels the scope immediately, awaited or not.- Blocking on
Dispatchers.Default. One blocked thread is a core gone.withContext(Dispatchers.IO), which also has a cap. - Shared mutable state with no protection.
counter++loses updates;@Volatiledoes not fix it.Mutex, atomics, confinement, or an actor. runBlockingin production code. Formainand tests. Bridge to blocking code at the edge, once.- Accidental serialization.
map { async { }.await() }is sequential. Launch all, thenawaitAll(). - Suspending in
finallyafter cancellation. Throws immediately. Cleanup that suspends runs inwithContext(NonCancellable). - Misplacing
SupervisorJob.launch(SupervisorJob())detaches the coroutine; it does not supervise its children. UsesupervisorScopeor a scope you own.
From “find the bug in this function” (about the debugging exercise itself):
- Rewriting instead of fixing. A rewrite hides whether you found the bug and usually adds a different one.
- Not naming the failing input. Say the input, the wrong output, the right output, then fix.
- Fixing the symptom. A guard that makes the example pass without addressing why the wrong value arose.
- Changing more than needed. A one-line bug gets a one-line fix.
- Not rerunning the original example after the fix. The ticket’s example still has to pass.
- Silent reading. Narrate the hypotheses as you discard them.
- Assuming the bug is where the interviewer pointed. Check the initialization above the loop too.
- Not asking what correct means. Touching intervals is a specification question, not a bug.
- Stopping after the first bug. Say “let me check for another,” and look.
- Not stating the test you would add. The regression test is the other half of the answer.
Disagreeing and Deciding
From “how do you disagree with a senior engineer?”:
- “I have never really had a disagreement.” Either nothing you worked on mattered, or you are not telling the truth.
- The hero story. “I knew it was wrong, I proved it.” Include the part where they had a point.
- Going around them. Escalating or lobbying before talking to the person is political. The first conversation is with them.
- Disagreeing in the wrong room. Contradicting them in front of their team turns a design question into a status contest.
- Opinion versus opinion. Without a number, a spike, or a document, rank decides.
- Confusing a preference with a risk. Spend pushback on decisions that are hard to reverse.
- Not asking what would change their mind. The question that converts an argument into an experiment.
- Relitigating after the decision. Every later meeting, or the quiet escape hatch. Everyone can see it.
- Caving silently. Concern stated once and dropped, with nothing on record when it happens.
- Making it about seniority. The rank is context. The story is the evidence and the decision.
- Not closing the loop. Without the outcome and the lesson, the story is a complaint.
Telling the Story
From “tell me about a time an architecture decision went wrong”:
- The fake mistake. A replaced logging library affected nobody’s career.
- The humble brag. “We grew faster than I designed for.” The interviewer has heard it a hundred times.
- Blaming. The team, the requirements, leadership. The frame is what you decided.
- Hindsight framing. If it was obviously stupid, why did you make it? Show it was reasonable, then show what was missing.
- No detection story. What did you observe, who noticed, how long did you keep going?
- No numbers. Months of migration, hours of outage, a delayed launch.
- The generic lesson. “I learned to plan more carefully” is an apology, not a lesson.
- The story where nothing could have been known. Bad luck teaches nothing about your judgment.
- Eight minutes of context. Lead with the headline.
- Naming names. Confidentiality is itself a signal about judgment.