cswork / libraryOpen practice workspace

Interview guides · Zynga

Zynga Software Engineer Interview Guide

Zynga software engineer prep: algorithm drills where every complexity claim has to name the assumption under it.

The 60-second brief

As a Software Engineer at Zynga, you play a central role in delivering interactive entertainment to tens of millions of daily active players worldwide. Whether you are building real-time multiplayer networking for competitive titles, crafting client-side game logic in Unity and C#, or architecting high-throughput backend microservices, your code directly influences player engagement, retention, and live operations. Zynga operates iconic franchises such as Words With Friends, FarmVille, CSR Racing, and Hit It Rich!, alongside high-scale central technology platforms that power telemetry, matchmaking, payments, and leaderboards across the entire portfolio.

If the loop includes an asynchronous take-home, treat it as a code review of you rather than as a puzzle: structure, what you chose to test, and what you wrote down about the constraint you were working under. Hold the stated time box and say what you would have done with more of it.

Focus areas

  • Prevent negative balances with conditional single-statement debits
  • Separate client-predicted state from durable ownership
  • Budget per-tick simulation, serialization and bandwidth

Overview

The Software Engineer position at Zynga spans client-side frontend development, server-side infrastructure engineering, and central systems development. You will collaborate closely with cross-functional teams including game designers, product managers, technical artists, and quality assurance engineers to turn creative concepts into reliable, production-ready gameplay features. Engineering at requires balancing rapid feature iterations with uncompromising performance constraints, managing memory footprints, and ensuring low-latency communication between game clients and cloud infrastructure.

What makes this role compelling is the sheer scale and immediate feedback loop of social and mobile gaming. A feature or optimization you deploy can instantly touch millions of concurrent users globally. Candidate evaluation reflects this reality: Zynga looks for engineers who combine strong computer science fundamentals, clear object-oriented design skills, and pragmatic problem-solving abilities with a passion for building engaging user experiences.

The Zynga interview process

Zynga candidates report 3 rounds · ≈ 3-5 weeks. The stages below are what candidates describe, not a published process.

HR Recruiter Screen

Half of this call is the part candidates treat as small talk: start date, notice period, work authorisation and its timing, location and time zone, on-call, and the number. Those are what kill offers late, after several engineers have each spent a day. Surfacing a hard constraint now costs you nothing and occasionally buys you something, since a loop compressed to fit a competing deadline can usually only be arranged if it is asked for early. The common failure is deflecting the compensation question twice, then discovering at offer stage that the band never reached your number.

What to demonstrate

  • Whether your hard constraints are compatible with the role before a loop gets booked: earliest start, notice period, what authorisation you hold and when it needs action, days on site, willingness to carry a pager
  • Whether you give a compensation range with something behind it, such as current total compensation or a competing timeline, rather than leaving the band untested
  • Whether your stated timeline is real, since a competing deadline raised now is something scheduling can sometimes work around and the same deadline raised at offer stage usually is not

How to prepare

  • Write each constraint down in one line before the call and state them as facts rather than negotiating them live under a question you were not expecting
  • Set your range from two or three current data points for that level and location, and name the structure you are quoting in, so the number is comparable to the one they are holding
  • If another process is running, say where it stands and by when, and ask directly whether this loop can be scheduled inside that window

Technical Assessment

Input bounds are the part of the prompt most often skimmed, and they usually contain the answer. They tell you which complexity class is admissible, which narrows the search before you have thought about the problem itself. As a rough planning figure, a compiled language does on the order of 10^8 simple operations per second and an interpreted one roughly an order of magnitude less. So n up to about twenty admits enumerating subsets, a few thousand admits a quadratic pass, and a million admits neither: you need near-linear, or linear with a log factor. If the bounds are missing, ask for them.

What to demonstrate

  • Whether the approach is justified by the stated input size rather than by whichever pattern you recognised first
  • Whether you ask about the properties that change the algorithm: whether the input arrives sorted, whether duplicates occur, whether values are bounded integers, whether it all fits in memory
  • Whether you can name the bottleneck in your own solution and what would remove it, even when you deliberately leave it in place
  • Whether a claimed speedup is real, since memoising a recursion only helps when subproblems genuinely overlap and the state can be keyed cheaply

How to prepare

  • For each algorithm you rely on, write down the largest n it handles in roughly a second, then check two of those figures by timing them in the language you will actually type in
  • For two weeks, write one line naming your target complexity and the bound that justifies it before you write any code, then compare that line with what you ended up submitting
  • Practise the conversion backwards: given a required O(n log n), list the mechanisms that get you there (sorting, a heap, an ordered map, divide and conquer) and choose by what the problem needs to query, not by what you used last

Comprehensive Interview Stage

Because the format is not fixed, the first job in the room is classification. Listen to the opening question and decide what it is: a probe into work you have already described, a fresh problem to solve now, or a conversation about how you operate. Each wants a different register, and the common failure is forcing a rehearsed structure onto a question that did not ask for it. Running a full design ritual on a ten-minute debugging question reads as not listening. When you cannot tell which it is, ask how long they want to spend and answer at that depth.

What to demonstrate

  • Whether the shape of your answer matches the question, so a yes-or-no gets answered before it is justified and an open prompt gets a direction before a detour
  • Whether you check how much depth is wanted instead of deciding for them, and whether you stop when the answer is complete rather than continuing until someone interrupts
  • Whether you can be redirected in the middle of an answer without restarting it from the beginning
  • Whether a question outside your experience gets an honest boundary followed by reasoning from what you do know, instead of a confident answer with nothing behind it

How to prepare

  • Rehearse one project at three lengths, roughly thirty seconds, three minutes, and a full walkthrough at the depth of a design review, and practise switching between them when someone interrupts mid-telling
  • Have someone ask you five questions of deliberately mixed type in one sitting without telling you the types, and score only whether you identified each one correctly before you started answering
  • Draft the sentence you will use to check depth, along the lines of asking whether the short version is useful here or they want the detail, and use it in a real conversation this week so the day of the round is not its first outing

Where candidates lose points

Read-modify-write on a balance or a stack quantity - SELECT the current value, check it in application code, then UPDATE to the computed new value.

The gap between the read and the write is a lost-update window of a few milliseconds. It survives every code review and every load test run at baseline concurrency, then fires reliably when a seasonal event puts two requests on the same wallet inside that window, or when one player has two clients open. The fix is one statement - UPDATE ... SET qty = qty - :n WHERE qty >= :n - plus an affected-row check; the trap is that the broken version returns the correct answer in every single-threaded test you will write.

Treating the message queue as exactly-once, so the reward consumer applies a grant without a dedupe check.

A broker that redelivers on consumer failure is at-least-once by construction, and a consumer that crashes between writing the grant and acknowledging the message will see that message again. Exactly-once end-to-end would need a transaction spanning the broker and the database, which does not exist across heterogeneous systems, so the sink has to be idempotent. The failure is invisible in steady state and shows up as a burst of duplicate grants exactly when a deploy or an OOM restarts consumers.

Saying 'eventually consistent' without naming the anomaly a user would see

Describe the concrete symptom you are choosing to accept: the author reloads and their own comment is missing for two seconds, or two devices show different balances for a minute. The class of consistency model is a technical label; the tolerable anomaly is the actual product decision.

Choosing a schema before the access patterns are known

Write the queries first, with their filters, sort orders, cardinalities and which ones sit on the latency-critical path, then design tables and indexes to serve them. An index nothing queries still costs write throughput and storage, and a hot query with no supporting index becomes a full scan that only hurts once the table is big.

Questions & practice

"Implement a queue data structure using two stacks and demonstrate the time complexity for push and pop operations."

Medium · data structures and algorithms

Approach

  1. State the target complexity and say which constraint rules the naive version out.
  2. Walk one small example through your approach before writing the whole thing.
  3. Name the brute-force solution and its complexity before improving on it.

Follow-up

  • How does this change if the input no longer fits in memory?
  • What is the worst case, and how likely is it on real data?

"Implement a function to check if two strings are valid anagrams of each other."

Medium · data structures and algorithms

Approach

  1. State the target complexity and say which constraint rules the naive version out.
  2. Walk one small example through your approach before writing the whole thing.
  3. Choose the data structure from the access pattern, not from familiarity.

Follow-up

  • Which test case would catch an off-by-one here?
  • How does this change if the input no longer fits in memory?

"Given a binary tree, write a function to perform a zigzag level-order traversal."

Medium · data structures and algorithms

Approach

  1. Choose the data structure from the access pattern, not from familiarity.
  2. Name the brute-force solution and its complexity before improving on it.
  3. Restate the input: its shape, its size, and what is guaranteed about it.

Follow-up

  • How does this change if the input no longer fits in memory?
  • What is the worst case, and how likely is it on real data?

Audit ledger legs for unbalanced transactions in one pass

Easy · ledger · hashing · reconciliation

You are handed one day's currency_ledger partition as a stream of legs: txn_id UUID, wallet_id, currency_code, delta_minor BIGINT (signed), reason. Legs arrive in arbitrary order, not grouped by txn_id. Reasons in MINT_SINK = {match_reward, loot_roll, purchase_fulfil, sink_craft, sink_repair, refund_reversal, operator_adjustment} deliberately create or destroy value. Every other txn_id must have legs summing to zero and must use a single currency_code. The partition holds 2.7 million legs. Return each offending txn_id with its residual, in one pass, and state your memory bound.

Approach

  1. One hash map keyed by txn_id holding (running sum, first currency_code seen, mint_sink flag, leg count). Single pass, O(n) time, O(T) space for T distinct txn_ids.
  2. Set the exempt flag the moment any leg of a txn carries a MINT_SINK reason, but keep accumulating the sum anyway so the residual is still reportable when an operator asks why a mint was that size.
  3. Do not evict a txn when its running sum reaches zero. Legs are unordered, so a later leg can unbalance a txn that momentarily netted out; eviction is only safe at end of partition, or behind a watermark plus a known expected leg count.
  4. Memory: 2.7 million txn_ids at roughly 16 B of UUID plus 8 B of sum plus 8 B of flags is about 86 MB before hash overhead. If that is too much, hash the UUID down to a 64-bit key and accept collision risk, or run k range-partitioned passes at k times the I/O.
  5. Report two distinct defect classes, not one: residual != 0 for a non-exempt txn, and more than one currency_code under a single txn_id. The second can occur with residual exactly 0 and is invisible to a pure sum check.

Worked solution (20 min)

  1. Build an eight-leg fixture: a clean trade (buyer -10000 market_purchase, seller +9500 market_sale, house wallet +500 market_fee), a fee-less trade (buyer -10000, seller +9500, no third leg), a mint (+250 match_reward), and a mixed-currency txn (+100 in one currency, -100 in another).
  2. Shuffle the eight legs so no txn's legs are adjacent, then run the single pass.
  3. Record the map contents after every leg and confirm the fee-less trade's running sum passes through a non-final value before settling.
  4. Re-run with an eviction-on-zero optimisation added and diff the output.

Expected result: Two txn_ids reported. The fee-less trade with residual -500 minor units, and the mixed-currency txn flagged on the currency rule with residual 0. The mint is exempt and the clean trade nets to 0, so neither appears.

Verify it yourself

  • The sum of all reported residuals equals the sum of every non-exempt leg in the fixture, because balanced transactions contribute exactly 0.
  • Shuffling the input order again changes nothing in the output set.
  • The eviction-on-zero variant loses the mixed-currency txn, which is the proof that the optimisation is wrong rather than merely unnecessary.

Follow-up

  • Run the same audit continuously over an unbounded stream with no partition boundary. What bounds the map now?
  • Two legs of one txn land in different daily partitions because the writer straddled midnight. What does your job report, and what do you change so it stops lying?
  • How do you tell a genuinely unbalanced txn from one whose compensating leg was written and later reversed by a new row?

Design the unique constraint that stops a reward duplicating

Medium · idempotency · at-least-once · partial index · reconciliation

grant_receipt(grant_id UUID PK, account_id BIGINT, idempotency_key TEXT NOT NULL UNIQUE, source_type TEXT, source_ref_id UUID, payload_hash BYTEA, status TEXT CHECK (status IN ('pending','applied','reversed','rejected')), created_at TIMESTAMPTZ, applied_at TIMESTAMPTZ NULL). purchase_order(order_id UUID PK, platform TEXT, receipt_id TEXT, capture_status TEXT, fulfil_status TEXT, fulfil_grant_id UUID NULL, captured_at TIMESTAMPTZ NULL). Match rewards arrive over an at-least-once queue; store receipts can be re-presented by a reinstalled client. Specify how idempotency_key is derived for a match reward and for an order fulfilment, what the insert does on a key collision, and write the sweep that finds captured payments never fulfilled.

Approach

  1. Derive the key from server state only: for a match reward, 'match:' || match_id || ':' || account_id || ':' || slot_index, taken from the signed match_result row; for fulfilment, 'order:' || platform || ':' || receipt_id, taken from the validated receipt. Nothing the client sends enters the key, so a client that regenerates a request id on retry cannot mint a second reward.
  2. Explain what one global UNIQUE (idempotency_key) buys: the source is encoded in the string, so a single index covers every source type, and the database rather than application code is the thing enforcing at-most-once. A broker that redelivers on consumer failure is at-least-once by construction, so the sink has to dedupe.
  3. Handle the collision correctly. On 23505, read the existing row and compare payload_hash: equal means a genuine retry, so return the original grant_id and the original contents; different means a key reused with different contents and must be rejected, not quietly honoured with the old payload.
  4. Know the ON CONFLICT sharp edge: DO NOTHING RETURNING grant_id returns zero rows on conflict and does not hand back the existing row. Either follow it with a SELECT, or use a no-op DO UPDATE to force RETURNING at the cost of a row lock and a dead tuple on every retry.
  5. Write the sweep for the opposite failure — captured but never fulfilled — with a grace window so it does not race an in-flight fulfilment, and a partial index on (captured_at) WHERE capture_status='captured' AND fulfil_status='unfulfilled' so it costs the size of the matching set rather than the table.
  6. Say what the sweep does on a hit: re-drive the same grant with the same derived key, which is safe precisely because the key is idempotent and a genuine double-run collides instead of double-granting.

Follow-up

  • A reward is granted, then the match's integrity_flag flips to voided. How do you take it back, and what does the player see?
  • Two reward slots on one match happen to resolve to identical items. Does your key still separate them?
  • The queue redelivers a batch of 50,000 after a consumer OOM. What does your database do for the next minute?

Soft-delete an item stack and keep one live row

Easy · soft delete · partial index · audit history

item_instance(instance_id UUID PK, owner_account_id BIGINT, item_def_id INT, item_def_version INT, quantity INT CHECK (quantity >= 0), bind_state TEXT, escrow_id UUID NULL, origin_grant_id UUID NULL, acquired_at TIMESTAMPTZ, updated_at TIMESTAMPTZ, consumed_at TIMESTAMPTZ NULL, row_version BIGINT). Rows are never hard-deleted: consuming a stack decrements quantity and sets consumed_at at zero, so the transfer chain stays replayable. Product requires at most one live stack per (owner_account_id, item_def_id, bind_state), with acquisitions merging into the existing stack. Write the constraint that enforces this without blocking re-acquisition after a consumption, plus the session-start inventory query over 10^2-10^4 stacks and the index that serves it.

Approach

  1. Show why the obvious UNIQUE (owner_account_id, item_def_id, bind_state) is wrong: consumed rows stay in the table forever, so the first time a player finishes a stack they are permanently blocked from ever acquiring that item again.
  2. Use a partial unique index — CREATE UNIQUE INDEX uq_live_stack ON item_instance (owner_account_id, item_def_id, bind_state) WHERE consumed_at IS NULL; — so consumed rows fall out of the index entirely while staying in the table.
  3. Say what you do on an engine without partial indexes: InnoDB has none, so the equivalent is a nullable generated column that is NULL once consumed_at is set, because a unique index treats NULLs as distinct and they never collide.
  4. Write the read as WHERE owner_account_id = :a AND consumed_at IS NULL AND escrow_id IS NULL ORDER BY acquired_at, backed by (owner_account_id, acquired_at) WHERE consumed_at IS NULL so the ordering comes from the index and no Sort node appears.
  5. Distinguish the two filters: escrowed stacks are hidden from the playable inventory but are still owned, so a supply audit or a support investigation must include them rather than reuse this predicate.
  6. Point at row_version as the optimistic token for equip and consume, not updated_at: now() is transaction-start time, so two updates in one transaction share a timestamp and timestamp equality is not a safe concurrency check.

Worked solution (20 min)

  1. Insert a stack, consume it by setting consumed_at, then insert the same (owner, def, bind_state) again and watch the full unique index reject it.
  2. Replace it with the partial unique index and repeat, confirming the re-acquisition now succeeds.
  3. Insert a second live stack of the same def and bind state and confirm it is still rejected.
  4. Run EXPLAIN on the session-start inventory query and check which index is chosen and whether a Sort node appears.

Expected result: CREATE UNIQUE INDEX uq_live_stack ON item_instance (owner_account_id, item_def_id, bind_state) WHERE consumed_at IS NULL; — re-acquisition after consumption inserts cleanly, a second live stack of the same def and bind state raises 23505, and the inventory read is an index scan with no Sort.

Verify it yourself

  • Consume-then-reacquire succeeds, while two simultaneously live stacks of the same def and bind state fail with 23505.
  • EXPLAIN on the inventory read shows an Index Scan on the partial index and no Sort node.
  • The consumed row is still selectable by instance_id and still carries origin_grant_id, so the chain back to the mint is intact.
  • Escrowed stacks are absent from the inventory result but present in a WHERE consumed_at IS NULL count.

Follow-up

  • A stack is escrowed and the listing expires unclaimed. Which row releases the lock, and what stops two sweep workers releasing it twice?
  • Support needs the full transfer chain for one instance. What does this model let them reconstruct, and what does it still lose?

"Design a web crawler system that handles asynchronous fetch requests and persistent document deduplication."

Medium · system design and architecture

Approach

  1. Choose a partition key and say what query it makes expensive.
  2. Fix the scope first: who calls this, how often, and what they do when it fails.
  3. State the consistency you need, and where you are willing to be stale.

Follow-up

  • How does this behave when that dependency is down for an hour?
  • What breaks first when traffic grows ten times?

"Describe the client-server synchronization strategy and data structures required to validate player moves in a real-time multiplayer game."

Medium · system design and architecture

Approach

  1. State the consistency you need, and where you are willing to be stale.
  2. Fix the scope first: who calls this, how often, and what they do when it fails.
  3. Choose a partition key and say what query it makes expensive.

Follow-up

  • What would you drop to keep the system up under load?
  • What breaks first when traffic grows ten times?

"Design a global leaderboard system for a mobile game with millions of daily players that supports real-time rank updates."

Medium · system design and architecture

Approach

  1. Name the failure you are designing for, then the recovery path.
  2. Fix the scope first: who calls this, how often, and what they do when it fails.
  3. State the consistency you need, and where you are willing to be stale.

Follow-up

  • How does this behave when that dependency is down for an hour?
  • What breaks first when traffic grows ten times?

Fundamentals questions verify your grasp of memory mechanics, operating systems, networking protocols, and object-oriented programming concepts.

Medium

Approach

  1. Say what you would check first and why it is the highest-information step.
  2. State your assumptions explicitly before working the problem.
  3. Clarify what is being asked and what a complete answer contains.

Follow-up

  • How would you know your answer was wrong?
  • What assumption would you test first?

Model a marketplace purchase so a lost response stays recoverable

Hard · saga · async operations · partial failure

POST /listings/{listing_id}/buy currently returns 200 or 500. The purchase runs a saga across the marketplace, inventory and ledger services - reserve the listing, debit the buyer, transfer the instance, credit the seller net of fee, release the lock - with market_listing.status moving through active, reserved, sold or cancelled and the row also holding expires_at. The caller is a game client on a mobile network that loses responses routinely. Redesign the contract so the client can always determine the true outcome, including after an app restart. Specify the resource shape, the observable states, the timeouts, and what the client renders in each state.

Approach

  1. State why the current shape is unrecoverable rather than merely awkward: a lost 200 and a genuine 500 are indistinguishable at the client, so there is no safe action. Blind retry double-debits, giving up strands the escrow. The outcome has to be readable, not inferred from a status code that may never have arrived.
  2. Promote the attempt to a resource. POST returns 202 with an attempt id and a Location, and the client polls or long-polls that resource to a terminal state. After an app restart the client re-reads the attempt by id; it never re-POSTs to find out what happened.
  3. Key the POST itself so the pre-id window is safe: idempotency on (buyer_account_id, listing_id, client_attempt_nonce), with the nonce persisted client-side before the request is sent. A re-POST before the id is known returns the same attempt rather than starting a second saga against the same listing.
  4. Enumerate the observable states and the client rendering for each, including the ones players hate: pending (in progress, funds may be held), reserved (the saga is past the point the client can abort, so do not offer a cancel button that cannot work), settled, failed with a reason code, expired. Ambiguity belongs in a named state, not in the absence of a response.
  5. Bound every wait with a server-side reaper, not with client behaviour. Attempts stuck in reserved past market_listing.expires_at are cancelled and the compensations run: release the lock, and reverse the debit as a new ledger leg with its own idempotency key rather than as an update. The compensations must be idempotent because the reaper can run twice over the same attempt.
  6. Make terminal recovery a read. After a failure that followed a debit, the client re-reads the attempt and the balance; it does not issue a corrective write. Any corrective write from a client is a second unsynchronised actor in a saga that already has enough of them.

Worked solution (45 min)

  1. Write the current call's two failure traces - lost 200 and real 500 - and show that the client cannot distinguish them from any information it holds.
  2. Define the attempt resource: fields, states, and the exact transitions, marking which transitions are driven by the saga and which by the reaper.
  3. Mark the crash point after each of the five saga steps and write what recovery does at each one, naming the compensation and its idempotency key.
  4. Write the client state machine: what it renders, whether it polls, and whether a cancel affordance is offered, for each observable state.
  5. Specify the timeouts - poll interval, long-poll ceiling, escrow expires_at, reaper period - and check that the reaper always fires after the escrow can no longer be settled, never before.

Expected result: POST /listings/{id}/purchase-attempts returns 202 with {attempt_id, state: pending} and a Location. GET on that resource returns state in {pending, reserved, settled, failed, expired} with a reason code on failure, and is the only way the client learns the outcome. The POST dedupes on (buyer_account_id, listing_id, client_attempt_nonce) with the nonce persisted before sending. A reaper cancels attempts still reserved past expires_at and runs idempotent compensations - release the escrow, reverse the debit as a new ledger leg keyed on the attempt id. The client recovers by reading the attempt, never by re-posting.

Verify it yourself

  • Killing the client at any point and restarting it yields the correct outcome from a single GET.
  • Re-POSTing before the attempt id is known produces one attempt and one escrow, not two.
  • Running the reaper twice over the same stuck attempt produces one compensation, not two reversals.
  • Every state the client can observe has defined copy and a defined set of affordances, including the state where cancel is impossible.

Follow-up

  • Two buyers POST against the same listing within a few milliseconds. What does each attempt resource show, and where is the contention resolved?
  • The buyer is debited and the seller credit fails permanently. Which state does the attempt land in, and what does the ledger look like afterwards?
  • How long do attempt resources live, and what does a client see when it polls one that has been reaped?

Session start latency jumped tenfold after an inventory refactor

Easy · n+1 queries · latency regression · query batching

After a release, p50 session-start time rose from 38 ms to 260 ms and p99 from 120 ms to 1.4 s. Inventory service CPU is flat; the PostgreSQL primary shows statement count up 30x with unchanged mean statement time and unchanged rows returned per statement. The release replaced one item_instance query with a repository layer that loads a stack, then fetches its item_definition row per stack. Accounts hold 10^2 to 10^4 stacks. Produce an ordered diagnostic checklist, the root cause, and the fix with its expected latency.

Approach

  1. Split the regression by layer before reading any code: compare service-side p50/p99 against database-side total time per request, taken as a pg_stat_statements delta across the release. Statement count up 30x with mean statement time flat means per-statement cost is fine and the count is the defect, so stop looking at indexes and plans.
  2. Confirm the shape: order pg_stat_statements by calls for the window. An N+1 appears as one normalised query whose calls track stacks-per-account rather than requests per second, with a tiny mean_exec_time — it never shows up in a slow-query log.
  3. Do the arithmetic rather than asserting it is slow: the cost is 1 + N serial round trips. At a 0.4 ms in-datacentre round trip and N = 500 stacks that is roughly 200 ms of pure waiting no index can remove, and it scales with account size, which is why p99 blew out further than p50.
  4. Collapse to two statements: load the stacks, then one SELECT over item_definition WHERE (item_def_id, item_def_version) IN (...) across the distinct pairs, and hydrate in memory. Distinct definitions per account are in the tens, so the second statement is a small keyed lookup.
  5. Respect the domain constraint in the batch key: the join is on (item_def_id, item_def_version), not item_def_id alone. Batching on the def id alone silently serves the current catalog version to stacks that were minted under an older one, which is a correctness bug dressed as a performance fix.
  6. Prevent recurrence with a per-request statement-count budget asserted in an integration test, so the next accidental lazy load fails the build instead of a release.

Follow-up

  • Definitions are nearly immutable. What changes if you cache them in-process, and what invalidates that cache when a new catalog version is published?
  • How would you bound the worst case for a 10^4-stack account, where even the two-query version returns a large result set on every session start?

Suggested practice checklist

Day one measures instead of guessing, under a fixed rubric, and the remaining hours are allocated in proportion to the gaps before any studying begins. The allocation is deliberately not renegotiated midweek, because the area that feels worst on day three is usually the one that is moving.

Day 1 — Diagnostic, scored before you study anything

  • Sit a 110-minute diagnostic in four blocks: forty-five minutes on two coding problems, twenty-five on one design prompt taken to interface and data model, twenty of short-answer fundamentals, and twenty delivering two behavioural answers aloud.
  • Score each block from 0 to 3 on a fixed rubric where 3 is correct and fluent, 2 is correct but slow or prompted, 1 is partially correct and 0 is stuck, grading the artifact rather than how the attempt felt.
  • Allocate days two to five in proportion to 3 minus each block's score, write the allocation down, and commit to leaving it alone.

Deliverable: A scored rubric and a fixed hour allocation for the rest of the week.

Day 2 — Largest gap: find the boundary rather than the subject

  • Split the weakest area into named sub-skills and rate each separately. For coding those are restating the problem, choosing the structure, stating the invariant, turning the invariant into loop bounds, handling empty and single-element input, and accounting for complexity out loud.
  • Attempt three items positioned just above where the rating drops off, and for each write the first move you failed to make.
  • Re-attempt one of them from blank four hours later with nothing open.

Deliverable: A sub-skill map with the two blocking sub-skills circled.

Day 3 — Drill the blocking sub-skill by repeating the shape

  • Do eight short repetitions of the same shape rather than eight different problems, so what gets practised is the pattern and not the puzzle.
  • State the rule you now hold in one sentence, then test it against a case built to break it, a sliding window over an array containing negative values, or a cache-aside read path whose invalidation message is dropped.
  • Have someone else read your one-sentence rule and find the precondition you left out.

Deliverable: One rule statement with its preconditions attached and one counterexample that would have caught the incomplete version.

Day 4 — Second gap, plus maintenance on the strongest area

  • Run the same sub-skill decomposition on the second-largest gap in half the time.
  • Spend twenty-five timed minutes on the block you scored highest, choosing the hardest item you can still finish rather than a warm-up.
  • Write whether each area fails you on recall, on setup, or on execution, and set the fix accordingly: repetition for recall, a written checklist for setup, timed work for execution.

Deliverable: A second sub-skill map plus a one-line failure diagnosis for each area.

Day 5 — The gap that is not a skill

  • Record one technical and one behavioural answer, then count two things in the playback: seconds before your first clarifying question, and sentences you began without knowing where they would end.
  • Practise saying that you do not know, followed by how you would find out, without letting it soften into a guess, and practise stating a complexity or an estimate before being asked for it.
  • Redeliver one answer under a hard ninety-second cap, which forces structure ahead of detail.

Deliverable: Two recordings with a counted reduction in time-to-first-question.

Day 6 — Retest under day-one conditions

  • Sit the same 110-minute structure with new prompts of comparable difficulty and score it on the identical rubric.
  • For any block that did not move, change the method rather than adding hours: a block stuck at 1 usually means the practice was too varied, not too short.
  • Write down which single block you would still lose the offer on.

Deliverable: A second scored rubric placed beside the first, with one named remaining risk.

Day 7 — Full loop under interview conditions

  • Run a sixty-minute mock over the two blocks that moved least, with an interviewer briefed to interrupt and change direction mid-answer.
  • Write the recovery script for going blank: restate the question, state your assumption, name the first thing you would check.
  • Say every rule from the week aloud without reading it, and cut any you cannot state in a single sentence, since a rule you have to reconstruct mid-answer will not survive an interruption.

Deliverable: A one-page card holding the recovery script and only the rules you could state from memory.

Scope is read from what you decided

Team size, service count and tickets closed say very little. Seniority shows in the decision you owned: what you chose not to build, which constraint you traded away, whose objection you had to resolve before anything could move. A large project where you executed someone else's plan is a small story.

"How do you handle a situation where an interviewer or team member disagrees with your proposed software architecture?"

Medium · behavioural and engineering judgement

Approach

  1. Name the disagreement and how you resolved it with evidence.
  2. Pick a story where you made the decision, not one where you watched it.
  3. Close with what you would do differently, concretely.

Follow-up

  • What would you do differently if you ran that again?
  • How did you know your change caused the improvement?

"Walk me through the most complex technical bug or production issue you personally resolved in a previous project."

Medium · behavioural and engineering judgement

Approach

  1. State the situation in two sentences and spend the rest on the reasoning.
  2. Give the blast radius: what could have broken, and what you measured.
  3. Name the disagreement and how you resolved it with evidence.

Follow-up

  • What would you do differently if you ran that again?
  • How did you know your change caused the improvement?

Unblock an engineer without taking the work away

Easy · mentoring · delegation · feedback

Prepare a four-minute account of unblocking another engineer on something you could have finished yourself in an afternoon. Constraint: the account must include what you deliberately did not do, plus evidence that the help transferred — a later change they made alone, a review where they caught the thing you would have caught. Deliverable: how you worked out whether they were blocked on a concept, on a tool, or on a decision they were not empowered to make; what you actually handed over; and how long you gave it before intervening differently.

Approach

  1. Separate the three kinds of block out loud, because each has a different remedy. A missing concept needs an explanation and a reference. A missing tool needs a demonstration — attaching a debugger, reading a query plan, reproducing the failure locally. A decision they are not empowered to make needs you to go and get the decision, not to coach them through waiting for it.
  2. Say what you withheld. Quietly writing the patch is the exact failure this question is built to detect, and 'we paired on it' is often a euphemism for holding the keyboard for ninety minutes.
  3. Give the handover artefact: the failing test you wrote for them to make pass, the three-line sketch on the whiteboard, the document link plus the one paragraph in it that mattered. Artefacts are checkable; 'I explained it to them' is not.
  4. State your time-box and what you did when it expired. Deciding that the date now outweighs the teaching moment is legitimate senior judgement, provided you said so to the person rather than silently taking over.
  5. Close with evidence of transfer at a later date and without your involvement. Without that, the story is about your patience rather than their capability.

Follow-up

  • They pressed on with an approach you think is wrong but workable. When do you let that ship?
  • How would your approach change if the person were more senior than you?