Interview guides · District by Zomato
District by Zomato Software Engineer Interview Guide
District by Zomato Software Engineer interview guide: notification system design, multi-channel delivery, distributed systems, DSA and a 7-day plan.
The 60-second brief
District by Zomato is described by candidates as a consumer platform whose backend handles food delivery traffic in real time. The features that candidates list for this role include notification services, order management and user engagement platforms, all of which have to cope with sharp traffic spikes while keeping latency low. A Software Engineer here is described as building and maintaining the backend services behind those features, working with product managers and other engineers, and following the code from design review through to ownership of what ships.
This guide is for candidates interviewing for the Software Engineer role at District by Zomato, including SDE-1 level applications. It covers the reported system design questions, the topics candidates connect with the loop (notification systems, high-level design, multi-channel delivery, DSA), the stack named in the role requirements (Java, Go or Python, relational and NoSQL databases, cloud and containers as extras), and a plan for the days before the interview.
No round names, order or count are reported for this loop. What candidates report is a mix of coding and system design, with the design questions centred on notification systems and distributed-systems trade-offs.
Focus areas
- Notification System Design
- High-Level Design (HLD)
- Multi-Channel Notifications
Overview
The reported questions all sit in system design, and two of the three are about the same system: a notification service for a food delivery platform, and how that service handles push, SMS and email. The third is broader, asking how you keep a distributed system both available and consistent. Candidates also describe example scenarios such as a sudden surge of notifications at peak hours and the trade-off between a message queue and direct service-to-service calls. If you can walk through one notification design end to end and then re-use its parts to answer those variants, you cover most of what is reported.
The role description candidates give is backend-heavy: services that take significant traffic spikes, low-latency paths balanced against scalability, and features such as order management and user engagement. That points your preparation at concrete building blocks: an ingestion API with an idempotency key, a durable queue, per-channel workers, retry with backoff and a dead-letter path, user preference storage, and the database choice behind each. Pick Java, Go or Python (the languages the requirements name) and be able to write the coding side in it without hesitation.
Coding is reported alongside design, with an emphasis on data structures and algorithms; candidates for SDE-1 positions mention that foundational knowledge matters as much as practical application. Spend design time on the order a structured answer follows: clarify load and constraints, define the API, sketch the high-level design, then go deep on one or two components. Say your assumptions aloud as you go, because a silent pause hides your reasoning.
Candidates also report questions about past projects, technical challenges and team disagreements, so keep two or three real stories ready with the decision you made and its result. The role asks for communication and for coping with shifting priorities; a story where a plan changed mid-way and you adapted is more useful than a generic success.
Where to focus your preparation
Candidates do not report a round-by-round sequence for this loop, so the single stage below groups what to prepare by the topics and questions that are reported.
Preparation focus
No round names, order, count or durations are reported for this loop, so treat the recruiter as the source for the format and ask whether each stage is coding, design or a conversation about past work. What candidates do report is the content: system design (notification systems, distributed systems, API design), data structures and algorithms, and questions about previous projects, technical challenges and disagreements with teammates. The three reported design questions are the notification system for a food delivery platform, handling push, SMS and email in one notification service, and ensuring high availability and consistency in a distributed system. Prepare by building one complete notification design you can narrate in order (requirements, API, high-level components, then failure handling and scale), and keep a coding language of your choice warm for algorithm problems.
What to demonstrate
- Whether you can structure an open-ended design: constraints first, API next, then components and their trade-offs
- Command of distributed-systems basics reported for the loop: CAP theorem, load balancing, sharding, caching and message queues
- Working code on data structures and algorithms in a language such as Java, Go or Python
- How clearly you explain past projects and technical decisions
How to prepare
- Ask the recruiter for the stage sequence and whether coding is on a shared editor or a whiteboard, then practise in that setup
- Rehearse the notification system design aloud from the order-placed event through to a delivered push, SMS or email, including retries and de-duplication
- Prepare the queue versus direct service-to-service comparison and the peak-hour surge answer, since candidates report both as example scenarios
- Practise DSA problems in one backend language and explain each choice of data structure and its complexity
Where candidates lose points
Jumping to boxes and arrows for the notification system without asking about load, latency or which events trigger a message
Open with a short list of questions: events that trigger notifications, expected peak rate, acceptable delay per channel, and whether losing or duplicating a message is worse. Write the answers on the board and design against them.
Treating push, SMS and email as one code path with a switch statement, so a slow SMS provider stalls everything
Describe a channel abstraction with an adapter per provider, a separate queue and worker pool per channel, and independent retry and failure handling. Say which channel falls back to which and what the user preference store decides.
Saying a queue 'makes it reliable' without addressing duplicates, ordering or poison messages
State the delivery guarantee you are choosing (usually at-least-once), add an idempotency key so a retried send is not delivered twice, and name the retry limit, backoff and dead-letter queue for messages that keep failing.
Answering the availability and consistency question with a memorised CAP definition and no example
Pick two data items in the notification system, such as a preference record and a delivery-status log, and say which needs strong consistency, which can be stale, and which replication or quorum setting you would use for each.
Solving the coding problem silently and presenting only the final code
State the brute-force approach and its complexity first, name the data structure that improves it, then code while narrating; finish by testing an empty input and a duplicate-heavy input.
Questions & practice
Identify the heaviest tenants in a five-minute window under memory pressure
Medium · top-k · heavy hitters · streaming
The edge service handles about 3,000 requests per second across roughly 50,000 tenants, peaking near 9,000. Expose the 50 heaviest tenants by request count over the trailing five minutes so limits can be tightened before one tenant's backfill starves the fleet. You may not retain five minutes of raw records. Give the exact solution and its memory, then the bounded-memory approximation with its error stated as a formula, and say which you would ship and at what tenant cardinality that choice changes.
Approach
- Do the exact version first, because it is affordable at this cardinality: a ring of 300 one-second counters per tenant, advanced lazily, is 1,200 bytes of counters per tenant and roughly 60 to 90 MB for 50,000 tenants with overhead. Carry a running total and subtract the bucket you overwrite so a window read is O(1) rather than 300 adds.
- Extract the top 50 with a size-k min-heap over the tenant sums: O(d log k) for d tenants, against O(d log d) to sort them all. Maintaining the heap continuously instead of on query requires a tenant-to-heap-index map, because incrementing a count already inside the heap means sifting from a known position, and without that map you rebuild the heap on every request.
- State the approximation precisely rather than gesturing at sketches. Misra-Gries with m counters retains every item whose true count exceeds N/(m+1), and each retained count underestimates the truth by at most N/(m+1). With m = 1,000 and N = 900,000 requests in the window the error is roughly 900 requests, which is fine for spotting a tenant sending 50,000 and useless for ranking two tenants 200 apart.
- Say what breaks when the window slides: Misra-Gries and Space-Saving are insert-only and cannot be decremented as records age out. The workable construction is one summary per sub-window, say ten seconds, with 30 summaries merged at query time, and the merged error is the sum of the per-summary errors, so the bound degrades linearly in the number of sub-windows.
Follow-up
- The heaviest tenant is heavy because of one export job rather than user traffic. Should the limiter treat those as the same tenant?
- Two tenants sit tied at the boundary of the top 50. Does your answer flap, and does the flapping matter?
Canonicalise a request body into a stable idempotency fingerprint
Medium · parsing · canonicalisation · hashing
idempotency_key.request_fingerprint is a SHA-256 over the method, path and canonicalised body, and a retry whose fingerprint differs must be rejected with 422 rather than served the stored response. Write the canonicaliser. Bodies are JSON up to 256 KB nested at most 32 levels; clients vary key order, whitespace and unicode escaping, and some send 64-bit ids as JSON numbers. Produce a deterministic byte string such that semantically identical bodies match and any semantic difference does not. State your complexity and name two normalisations you refuse to perform.
Approach
- Parse once into a tree, then re-serialise under fixed rules: object keys sorted, array order preserved, one escaping convention, no insignificant whitespace. Parsing is O(n) and sorting keys is O(k log k) per object, so O(n log n) overall with O(depth) stack, and the 32-level cap is enforced during parsing because hostile nesting is how a canonicaliser becomes a stack overflow.
- Sort keys by their UTF-8 bytes and say why the obvious implementation is wrong in some runtimes: a default string comparison that orders by UTF-16 code units places surrogate pairs, meaning code points from U+10000 up, below U+E000 to U+FFFF, which is not UTF-8 byte order, so two services written in different languages disagree on the same document.
- Do not re-encode numbers through a double. IEEE-754 binary64 represents integers exactly only up to 2^53, so normalising a 19-digit id through a float changes it, and 1 against 1.0 cannot be reconciled without deciding whether they are the same value. Preserve the literal token, and require ids as strings at the API boundary if you want them comparable.
- Reject duplicate keys rather than picking one. JSON permits them and parsers disagree, most keeping the last, so any choice you make ties the fingerprint to a parser detail that the code handling the request does not necessarily share.
Follow-up
- A client sends the same logical request with an extra field your API ignores. Same key, different fingerprint, so you return 422. Is that the right answer?
- Where does the fingerprint get computed relative to request decompression and the body-size limit?
Find overlapping job attempts and peak concurrency from lease records
Medium · sweep line · intervals · leases
A day of job_run history yields about 50,000,000 attempt records: (job_run_id, job_type, attempt, started_at, finished_at which is NULL when the worker died, lease_expires_at). Leases expire on a clock, so a job that outran its lease ran twice. Produce (a) every job_run_id whose attempts overlapped in wall-clock time and (b) the peak number of simultaneously running attempts per job_type with the minute it occurred. Target O(n log n). State how you treat a NULL finished_at and what clock skew does to your answer.
Approach
- Define the interval before sorting anything: an attempt occupies [started_at, COALESCE(finished_at, lease_expires_at)). finished_at is observed and lease_expires_at is only a promise, so every attempt without a finish contributes an estimate and the whole result is a lower bound on overlap rather than an exact count.
- For peak concurrency, sweep: emit 2n endpoints, sort by (timestamp, kind) with ends ordered before starts at equal timestamps, then walk the sequence maintaining a counter per job_type and record each type's maximum with its timestamp. O(n log n) dominated by the sort, O(n) space, or O(1) extra if the sort is external and the walk streams.
- For overlap detection, do not compare attempts pairwise. A single global sort by (job_run_id, started_at) gives both the grouping and the order; within a group, keep the maximum end seen so far and report an overlap exactly when the next start is less than that running maximum, which is one linear pass after the sort.
- Half-open intervals matter and are easy to get wrong: with closed intervals an attempt ending at the same millisecond another begins reads as concurrency two, and across 50,000,000 records that artefact swamps the real signal.
Follow-up
- A handler is not idempotent and you have found 400 overlapping jobs. Which of them actually caused damage, and what would you query to find out?
- Peak concurrency for one job_type is 4 against a configured cap of 4. Is the cap working, or is the data hiding attempts that never started?
Find version gaps and relay lag with window functions
Medium · window functions · event tables · frame semantics
outbox_event holds event_id, aggregate_type, aggregate_id, aggregate_version, event_type, payload, status ('pending','published','dead'), attempts, created_at, published_at. A projection is missing rows and you must decide whether the relay skipped events or the consumer dropped them. Write three queries over the last seven days: one listing every aggregate_id whose published aggregate_version sequence has a hole, one giving per-day counts with a running total, and one returning the newest published event per aggregate. For each, say where the window function is evaluated relative to WHERE and LIMIT. PostgreSQL 16.
Approach
- Gaps: compute lead(aggregate_version) OVER (PARTITION BY aggregate_id ORDER BY aggregate_version) in a subquery, then filter next_version <> aggregate_version + 1 in the outer query. Window functions are evaluated after WHERE, GROUP BY and HAVING and before the outer ORDER BY and LIMIT, so the predicate cannot sit in the same WHERE clause and PostgreSQL 16 has no QUALIFY.
- Say what the seven-day filter does to the answer: it truncates every partition, so the first row per aggregate has no predecessor inside the window and a hole spanning the boundary is invisible. Widen the window, or join to resource.version as the authority for the true maximum.
- Running total: SELECT date_trunc('day', created_at) AS d, count() AS n, sum(count()) OVER (ORDER BY date_trunc('day', created_at) ROWS UNBOUNDED PRECEDING). An aggregate inside a window call is legal because grouping runs before windowing. The grouping key is unique per row here so ROWS and RANGE agree, but write the frame anyway — over ungrouped rows with tied timestamps the default RANGE frame pulls in every peer row and the total jumps.
- Newest per aggregate: DISTINCT ON (aggregate_id) ... ORDER BY aggregate_id, aggregate_version DESC is the cheap PostgreSQL-only form when an index matches that order; row_number() OVER (PARTITION BY aggregate_id ORDER BY aggregate_version DESC) = 1 is the portable form and needs a subquery for the same evaluation-order reason as the gap query.
Follow-up
- Relay failover redelivers events. Does a duplicate break the gap query, and how would you detect one from this table alone?
- Turn the gap check into a continuous monitor rather than a query someone runs after an incident. What does it watch?
Replace offset paging on the resource feed with keyset
Easy · keyset pagination · composite index · sort order
resource holds resource_id, tenant_id, owner_user_id, title, body_ref, version, status ('draft','active','archived','deleted'), created_at, updated_at, deleted_at, with an index on (tenant_id, status, updated_at DESC, resource_id DESC). The listing endpoint returns active resources for one tenant, newest update first, 50 per page, today with LIMIT 50 OFFSET n. Tenants reach page 400 and rows are created while they read. Write the keyset query, define what the cursor carries and how it is encoded, and say which part of the index each predicate uses. Assume PostgreSQL 16.
Approach
- Name the two failures separately. OFFSET 20000 makes the server produce and discard 20,000 rows, so page cost grows with depth rather than with page size. Independently, any write that changes how many rows sort above the offset moves the window between two fetches, and the direction decides which anomaly you get: an insert lands at the head of updated_at DESC and pushes already-returned rows down past the boundary, so they are returned a second time; a delete above the offset, or a row whose updated_at is bumped above the cursor, pulls rows up and one is never returned at all. Nothing in the response reveals either.
- Write the seek: WHERE tenant_id = $1 AND status = 'active' AND (updated_at, resource_id) < ($2, $3) ORDER BY updated_at DESC, resource_id DESC LIMIT 50. The row-value comparison is one index range rather than a disjunction, and both columns are NOT NULL, which is what makes that comparison well defined.
- Map each predicate onto the index: tenant_id and status are equality on the leading columns, (updated_at, resource_id) is the range, and the ORDER BY matches the index order so no Sort node appears and the scan stops after 50 rows. The DESC in the definition only matters for mixed directions — a plain ascending btree on the same columns is read backwards for this query.
- Put both sort columns in the cursor and nothing the client can tamper with into another tenant: base64 of (updated_at, resource_id), validated server-side, with tenant_id taken from the principal.
Follow-up
- The client asks for 'jump to page 400'. What do you offer instead, and what does the honest version cost?
- Sort order becomes user-selectable across four columns. How many indexes is that, and which would you refuse to add?
Design a notification system for a food delivery platform.
Medium · System Design and Architecture
Approach
- Pin the scope before drawing boxes: which events trigger a message (order placed, rider assigned, out for delivery, delivered, refund issued, promotions), which are transactional and which are marketing, which channels apply, and a rough peak rate. Then state a latency target for transactional updates and a looser one for promotions.
- Core path: the order service publishes status events to a durable log such as Kafka, partitioned by order_id so each order's events stay in sequence. A notification service consumes them, looks up user preferences and device tokens, renders a template and enqueues one job per channel, and channel workers then call APNs/FCM, an SMS gateway or an email provider.
- Delivery is at-least-once, so make sends idempotent. Use a key of event_id plus channel, check it in a dedup store (a Redis SET NX with a TTL, or a unique constraint) before calling the provider. Retry with exponential backoff and jitter, and route jobs that keep failing to a dead-letter queue for inspection and replay.
- Keep transactional and promotional traffic apart: separate topics or queues with their own consumer pools, so a large campaign cannot delay a 'rider is arriving' push. Add per-user frequency caps and quiet hours for marketing only, and autoscale consumers on queue lag.
- Handle staleness. Attach an order status version to each message, skip any update older than the latest one already sent (no 'out for delivery' after 'delivered'), and give time-sensitive messages a TTL so a backlog drains by dropping them rather than sending them late.
Follow-up
- A promotional blast to millions of users lands during the dinner peak. How do you keep order-status messages on time?
- A worker crashes after the provider accepted a push but before it recorded the send. What happens on retry, and how do you stop a duplicate?
- How would you store notification history so the app can show an inbox with read and unread state?
How would you handle multiple delivery channels (push, SMS, email) in a notification service?
Medium · System Design and Architecture
Approach
- Put a routing layer in front of the channels. For each notification type it decides which channels are eligible, then filters by user preference and consent (SMS opt-in, email unsubscribe, push permission) and by whether a valid device token, phone number or email address exists. Every provider sits behind one adapter interface, for example send(message) returning delivered, retryable or permanent failure.
- Give each channel its own queue and worker pool, with its own rate limit, retry policy and timeout. A slow SMS gateway then backs up only SMS and never push. Put a circuit breaker on each provider and keep a secondary SMS or email provider to fail over to.
- Render templates per channel and per locale. Push has short title and body limits. SMS is 160 GSM-7 characters per single segment (70 if any character forces UCS-2), and longer texts are billed as several segments. Email needs an HTML body plus a plain-text alternative.
- Feed provider responses back into state. Delete tokens that APNs reports as 410 Unregistered or FCM reports as UNREGISTERED, ingest delivery receipts from provider webhooks, and keep a status per (notification, channel) row: queued, sent, delivered, failed.
- For critical messages, define escalation. If a push is not delivered or opened within a set window, send SMS. Set an idempotency key per notification and channel so the escalation cannot fire twice, and gate SMS deliberately because each message costs money.
Follow-up
- The primary SMS provider starts timing out on a third of requests. What does the system do automatically, and what alert fires?
- A user has disabled push and opted out of SMS, and their order has been cancelled. What, if anything, do you send?
- How would you notice a provider that reports success but silently drops messages?
How do you ensure high availability and consistency in a distributed system?
Medium · System Design and Architecture
Approach
- Split the data by what it needs. Order and payment state must not diverge, so it gets strong consistency. Restaurant listings, counters, recommendations and notification read state can be eventually consistent. Use CAP for the partition case (refuse the write or accept possibly stale data) and PACELC for the normal case (latency against consistency).
- Availability comes from removing single points of failure. Run stateless services in several availability zones behind load balancers with health checks, and use replicated databases with automated failover. Calls between services need timeouts, retries with backoff and jitter, circuit breakers and bulkheads, and the product should degrade (show cached menus, queue the notification) instead of failing.
- Then the consistency mechanisms. Single-leader replication sends writes to one node, with a synchronous replica if acknowledged writes must survive failover. In leaderless stores, quorums with R + W > N make every read set overlap the latest write set. Leader election and configuration go through a consensus system such as Raft (etcd) or ZooKeeper.
- Across services, avoid two-phase commit on the request path. Write the state change and an outbox row in one local transaction and have a relay publish the outbox. Make consumers idempotent, and use a saga with compensating steps (refund if the restaurant rejects). Use optimistic concurrency with a version column to stop lost updates.
- Name the failure modes. Asynchronous replication can lose acknowledged writes on failover (RPO above zero). Split brain is prevented with leases and fencing tokens. Replica lag breaks read-your-writes unless you read from the leader or carry a version token, and retries without idempotency keys turn one charge into two.
Follow-up
- The primary database fails over to an asynchronous replica. What can be lost, and how would you bound or detect it?
- How do you keep an order's status consistent between the order service and the notification service without a distributed transaction?
- Which parts of a food-ordering flow would you allow to be stale, and how stale?
Exports duplicate a row range about once a week
Hard · leases · clock skew · idempotency
Roughly once a week an export writes a file containing a duplicated range of rows. The affected job_run rows show attempt = 1, status = succeeded, one started_at, and a lease_owner naming a different host from the one whose logs show the job starting. Leases last 30 seconds and are heartbeated every 10 from inside the handler; lease_expires_at is computed on the worker and compared against the database's now(). Find the mechanism, and give a fix that holds even if you cannot fix the clocks.
Approach
- Start from the fact that eliminates the obvious answer. attempt = 1 means no retry was recorded, so this is not a re-run after failure; two workers ran the same row concurrently and the takeover path never touched the counter. lease_owner naming a host other than the one that started the job is the same statement from the other side.
- Enumerate the mechanisms that cause a premature takeover, then find the signal that separates them. Either the lease genuinely expired because the heartbeat did not fire, which is what happens when the heartbeat runs on the handler's own thread and the handler makes a long blocking call, or it only appeared expired because two clocks disagree, since lease_expires_at is written from the worker's clock and evaluated against the database's. The discriminator is the distribution: incidents clustered on the longest exports indict the heartbeat, incidents clustered on one host indict skew. Measure both, and measure each host's offset against the database directly.
- Read the reclaim query precisely. In PostgreSQL now() is transaction start time, not statement time, so a reclaimer holding a long transaction compares against an older timestamp than expected; clock_timestamp() is the statement-time function. This is worth ruling in or out before you redesign anything, because it changes which rows look expired.
- Remove the second clock rather than trying to synchronise it. Issue and extend the lease in the database, with lease_expires_at = now() + interval '30 seconds' in both the claim and the heartbeat, so exactly one clock is ever compared and worker skew stops mattering to this predicate.
Follow-up
- The displaced worker has already streamed half the file to object storage. What makes that side effect safe to repeat?
- You now count takeovers. What alert fires on that counter, and at what threshold?
Suggested practice checklist
The plan starts with the reported notification design, widens to multi-channel delivery, surge handling and distributed-systems theory, then spends two days on coding and data-layer practice before a full dry run. Days that practise reported or practice-set questions list them.
Day 1 — Notification system: requirements and API
- Write the functional and non-functional requirements for a food-delivery notification service: which order events trigger a message, peak rate, latency per channel, and how bad a duplicate or lost message is
- Define the ingestion API with an idempotency key, a user-preferences endpoint and a delivery-status endpoint, with request and response fields
- Sketch the high-level design with the API service, a durable queue, workers and a store for status and preferences
Deliverable: A one-page requirements list, an API contract and a high-level diagram for the notification system.
Day 2 — Multi-channel delivery: push, SMS and email
- Design a channel abstraction: one adapter per provider, one queue and worker pool per channel, so a slow provider stays isolated
- Write the retry policy per channel: backoff, retry limit, dead-letter queue, and when to fall back from one channel to another
- Decide where templates, localisation and user opt-out checks live in the flow
Deliverable: A per-channel flow diagram with retry, fallback and dead-letter rules written next to each channel.
Day 3 — Peak-hour surges and queue versus direct calls
- Prepare the answer to a sudden surge of notifications at peak hours: queue buffering, autoscaled workers, priority lanes for order-status messages over promotions, rate limiting and backpressure
- Write a comparison of a message queue and direct service-to-service calls covering coupling, latency, failure isolation, ordering and operational cost
- Rework your day 1 design to add the surge controls and say what degrades first under overload
Deliverable: A short comparison table (queue versus direct call) and a revised design with surge controls marked.
Day 4 — Availability and consistency in distributed systems
- Summarise CAP in your own words and attach one concrete example from the notification system to each side of the trade-off
- Cover the tools by name: load balancing, replication, sharding with a partition key, caching and its invalidation, and leader election or quorum writes
- Pick a relational or NoSQL store for the preference data and for the delivery log, and justify each choice by its access pattern
Deliverable: A two-minute spoken answer to the availability-and-consistency question, with a store choice and justification for two datasets.
Day 5 — Data structures and algorithms in your backend language
- Choose Java, Go or Python and write the practice problem on heaviest tenants in a five-minute window under memory pressure, stating the complexity
- Write the idempotency-fingerprint practice problem, which maps directly to de-duplicating retried notification sends
- Write the overlapping-attempts and peak-concurrency practice problem, then test an empty list and identical timestamps
Deliverable: Three solved practice problems in one language, each with its complexity and edge cases listed.
Day 6 — Data layer and your own project stories
- Work the keyset paging practice problem and relate it to paging through a user's notification history
- Write two project stories with the decision you made, the trade-off, and the result, plus one story about a disagreement with a teammate
- Work the outage practice prompt, then note which story would answer a question about a technical challenge you handled
Deliverable: A keyset-paging query, plus three written stories (two projects, one disagreement) each with a result.
Day 7 — Full dry run
- Speak the entire notification design aloud, from clarifying questions to failure handling, then answer the multi-channel and availability questions back to back
- Solve one fresh algorithm problem while narrating, ending with a complexity statement
- Write the questions you will ask the recruiter about the stage format and the questions you will ask the team about the services you would own
Deliverable: A recorded or written full mock with a list of gaps to fix, and your questions for the recruiter and the team.
Behavioural preparation
Candidates report that, next to the technical stages, you may be asked about past projects, how you handled technical challenges and team disagreements. The role description mentions working with product managers and other engineers, taking ownership of features, and coping with shifting priorities, so choose stories that show your own decisions and what changed because of them.
- Walk through a backend project you owned: what you built, which design decisions were yours, and what you would change now.
- Tell me about a technical challenge you handled, such as a latency or scaling problem, and how you found the cause.
- Describe a disagreement with a teammate over a technical approach: what each side argued, how you settled it, and what happened afterwards.
- Tell me about a time priorities shifted in the middle of a project and how you adjusted your plan.
- Describe a bottleneck or risk in a system you noticed before it caused an incident, and what you did about it.
Tell callers you do not own that their integration breaks
Medium · deprecation · compatibility · stakeholders
A field in a write endpoint's response must change shape. You own the endpoint; you do not own the four internal callers or the outbound webhook consumers who read it. Describe a deprecation you were responsible for: what you shipped first, how you established who was actually reading the field, the window you gave and what set its length, what you did about the consumer who never moved, and how you decided removal was safe. Name the signal you used, not the announcement you sent.
Approach
- Establish the reader set empirically rather than from a wiki of owners: per-field usage counters keyed by principal, or access logs attributed to a consumer. State the blind spot of whichever you pick, since a consumer that reads the field only on a monthly job will not appear in a week of logs.
- Ship additive first. Populate the new field alongside the old one so no reader is forced to move, which is also what keeps a rolling deploy safe, because old and new instances answer the same requests at the same time and a rollback must still find the old shape present.
- Set the window from the slowest legitimate consumer's release cadence, not from your calendar, and decide separately what to do for a consumer with no release process at all, such as an external webhook endpoint you can only email.
- Convert silence into evidence before you rely on it: a short, low-traffic removal window that makes a still-dependent consumer fail visibly and loudly while you are watching, rather than at three in the morning after you have moved on.
Follow-up
- How would you detect a consumer that reads the field only during a monthly export?
- One caller refuses to move and has a commercial relationship behind it. What changes in your plan and what does not?
Narrate an outage you owned from page to postmortem
Hard · incident response · blast radius · postmortems
Pick an incident you personally drove, ideally one where writes were affected rather than reads. In six to eight minutes: state the symptom as it first appeared on a dashboard, the blast radius you established before you knew the cause, the mitigation you applied and when, the mechanism you eventually proved, and the follow-up that would prevent a repeat. Bring numbers: error rate, tenants affected, minutes to mitigate, minutes to resolve. If you cannot name what you measured, choose a different incident.
Approach
- Open on the signal rather than the cause: which metric at which percentile moved, on which service, at what time, so the listener follows the same evidence you had rather than a conclusion you already reached.
- Separate mitigation from diagnosis out loud. State what you did to stop the bleeding (flag off, shed traffic, drain a lease, roll back a deploy) and say plainly that you did it before the mechanism was known, because those are two jobs with different deadlines.
- Establish blast radius in countable terms: how many tenants, how many writes, and crucially whether the effect was loss or only delay. An append-only revision table or a pending outbox row means the change survived and the projection was merely behind, which is a repair rather than a data-loss incident.
- Prove the mechanism instead of asserting it. Name the trace span that grew, the plan that flipped to a sequential scan, the lease that expired, plus one alternative you ruled out and the signal that stayed flat while you ruled it out.
Follow-up
- What would you do differently in the first five minutes, given the same dashboard and no more information?
- Which follow-up action did you deliberately not take, and why was dropping it the right call?
Reverse your own decision and price the reversal
Medium · reversibility · measurement · migrations
Describe a technical decision you made and later reversed. Pick one that cost something: a service you split and merged back, a cache you added and removed, an index you created that pushed the planner onto a worse plan, a projection you rebuilt from scratch. State what you believed when you decided, the measurement that changed your mind, how long the wrong version ran in production, and what the reversal cost in migrations, dual writes, and a deprecation window for callers you did not own.
Approach
- State the original rationale without irony, in the version you would still defend given what was known then. If it is not defensible, the story is about carelessness rather than judgement, and a different example serves you better.
- Give the measurement that moved with a before and after: the p99 that did not improve, the cache hit rate that sat at 40%, the plan that flipped to a sequential scan once the table passed a size you can name.
- Cost the reversal in steps, not adjectives: expand-and-contract deploys, the dual-write window, the callers who had to be notified, the rows already written in the wrong shape that had to be backfilled or abandoned.
- Distinguish reversal from rewrite by naming what you kept. Most good reversals preserve the schema or the interface and undo one decision inside it, which is also why they were affordable.
Follow-up
- What in that decision was irreversible, and did you know it was irreversible when you made it?
- How did you tell the people who had already built on top of the original decision?