Interview guides · Drw
Drw Software Engineer Interview Guide
Drw Software Engineer interview prep: the online assessment, phone screen, take-home and Superday, plus low-level, concurrency and design practice.
Interviewing at Drw
- Practice bank: 8+ questions
- Rounds: 3
- Typical prep: 2–4 weeks
- Interview reports: 6
Interview rounds
| Round | Questions |
|---|---|
| HR Screen | 2 |
| Online Assessment | 3 |
| Technical Screen | 3 |
How hard is the Drw Software Engineer interview?
| Difficulty | Share | Questions |
|---|---|---|
| Easy | 38% | 3 |
| Medium | 50% | 4 |
| Hard | 13% | 1 |
What Drw actually tests for
| Topic | Share | Questions |
|---|---|---|
| Coding & Algorithms | 100% | 8 |
The 60-second brief
Drw is a principal trading firm, so its software is the business itself rather than a product sold to outside users. Software Engineers there build systems that connect to exchanges, calculate real-time risk, process market data streams and run quantitative trading strategies across equities, fixed income, commodities and cryptoassets. Teams range from C++ and Rust engineers working on exchange gateways and execution systems to C#, Java, Python and Clojure engineers building analytics platforms and trading tools.
Drw candidates report 4 rounds · ≈ 3-5 weeks. The stages below are what candidates describe, not a published process.
What a Software Engineer does at Drw
The language spread is wide: C++ and Rust for exchange gateways and execution systems, and C#, Java, Python and Clojure for distributed analytics platforms and trading tools. That spread shows up in the reported questions. Some are language-neutral algorithms, and others ask how a vtable dispatches a call, how the JVM allocates and collects objects, or how a mutex and a condition variable are built.
For preparation, expect the reported questions to go below the level of "it works". Reported topics include virtual memory and process address spaces, thread synchronization primitives, CPU cache locality, the TCP handshake and socket buffers, and designs such as an order execution engine or a trade logging service that must not lose data in a crash. Pick one primary language and learn its runtime and standard library well enough to explain what your code does in memory, not only what it returns.
The Drw interview process
Drw candidates report 4 rounds · ≈ 3-5 weeks. The stages below are what candidates describe, not a published process.
Online Assessment
Candidates describe a proctored online assessment on Codility or HackerRank, with video and microphone enabled, that tests algorithmic problem solving, array manipulation and dynamic programming. Practise as if you cannot look anything up: standard-library calls, loop bounds and DP recurrences should come from memory, and the camera and platform setup should not be new to you on the day.
What it evaluates
- Algorithmic problem solving and array manipulation, the areas candidates name for this stage
- Dynamic programming: whether you can define the state, write the recurrence and turn it into an iterative table without a reference
- Correctness on edge inputs such as empty arrays, a single element, repeated values and values at the edge of the input range
How to prepare
- Solve a few array problems and one of the bank's digit-string problems in a browser editor with the camera on and no other windows open, so the proctored setup is familiar before the real one
- Write three DP problems from a blank file, including a stock buy-and-sell variant, stating the state, the transition and the base case before writing any code
- For every failed submission, write down the smallest input that fails and its expected output before you edit anything
Technical Phone Screen
Candidates describe a technical phone or video call with an engineer or a hiring manager covering core CS fundamentals, memory layout, language details and a review of past technical work. The reported low-level and fundamentals categories fit that description: virtual memory and address spaces, vtables and their overhead, how mutexes, condition variables and atomics work, JVM allocation and garbage collection, process scheduling and context switches, and cache locality. Prepare each answer as a mechanism, a cost and an example that ties the two together.
What it evaluates
- Whether you can explain the mechanism under a language feature (dynamic dispatch, heap allocation, a lock) instead of naming the feature
- Grasp of memory layout: stack versus heap, virtual-to-physical translation, and why contiguous data is faster to scan than linked data
- Whether your account of a past project names your own decisions and the trade-offs behind them
How to prepare
- For each reported low-level question, write a spoken answer with three parts: what the runtime or OS does, what it costs, and one situation where that cost matters
- Pick the one language you will be questioned in and review its standard containers: layout, growth policy, iterator invalidation, and the complexity of each operation
- Prepare a walkthrough of your most technical project that names two decisions you made, the options you rejected and what each one would have cost
Take-Home Coding Project
Candidates report that, depending on the team, some get a take-home project, with examples such as a game engine variant, an execution bot or a data processor, while others go straight to the Superday. Candidates also describe the Superday as including a code review of the take-home, so expect to discuss the submission and be ready to change it for a new requirement. Make naming, module boundaries, boundary tests and extensibility easy to see in what you submit, and treat it as code you will have to modify in front of someone, not just code that passes.
What it evaluates
- Idiomatic style in the language you were asked to use: naming conventions, small functions and a clear module layout
- Test coverage that reaches boundary conditions, not only the example in the prompt
- Whether the design can take a new requirement, such as streaming input instead of a batch, without a rewrite
How to prepare
- Before submitting, list the two changes a reviewer is most likely to ask for and check that each one touches a single module
- Write tests for empty, single-element, duplicate and malformed inputs, then break the code on purpose and confirm at least one test fails
- Include a short README covering your assumptions, what you left out and why, and how to run the tests
Superday / Onsite Interview
Candidates describe the Superday as several back-to-back technical sessions: live whiteboard or paired coding, a code review of your take-home if you had one, system design, and behavioral conversations with team members and leadership. Reported design questions include an object-oriented order execution engine handling thousands of quotes per second, a risk and P&L schema and API for historical and what-if scenarios, a low-latency trade logging service that must lose nothing in a crash, and a lock-free queue for dispatching orders across threads. Reported behavioral questions ask about your most complex project, debugging a production issue under time pressure, ambiguous requirements from traders or researchers, and why principal trading interests you.
What it evaluates
- Object-oriented design with clear class responsibilities, interfaces and error handling for a trading-style component
- Thread safety and durability: who owns shared state, and what has to be written and flushed before a trade counts as recorded
- Whether you can extend or defend your take-home code live when the requirements change
- How clearly you explain trade-offs in past work and handle ambiguous requirements
How to prepare
- Design the order execution engine from the reported question on paper: the classes, the threading model, and where a lock or a queue sits on the hot path
- For the trade logging question, explain step by step what happens to a record between the API call and durable storage, and which crash each step survives
- Practise two design problems and a coding problem back to back, so the multi-session format is not new on the day
- Prepare the four reported behavioral stories with a concrete decision and an outcome in each
Questions Drw candidates report
Data structures and algorithms
One structure rarely covers the whole operation set, and the interesting questions in this category are the ones where it does not. When you need constant-time lookup and constant-time reordering at once, or the minimum plus deletion of an arbitrary element, the answer is two structures that index each other: a map from key to the node holding it, beside the list or heap that maintains the order. What separates a strong answer is the invariant between them, stated explicitly. Every operation has to leave both halves consistent, and the bug people ship is a removal from one that leaves a stale entry in the other.
- Implement a custom binary search tree traversal or serialization method from scratch without relying on external libraries.
- 1–2 sentences introducing the category and what it tests. This category evaluates your ability to translate complex logic into clean, efficient code under time constraints, with a focus on data structures, time complexity, and edge case handling.
System design and architecture
Turn the prompt into a workload before drawing anything: requests per second at peak, the read to write ratio, the size of one stored object, how long it is kept, and how each of those grows. Those numbers decide the architecture, and without them every component is a guess. Size the simplest thing that fits, then add a tier only where a number says the simple version breaks. What separates a strong answer is that every box has a quantity attached to it. A cache, a queue and a partition scheme that each answer a specific number read as judgement; the same diagram without numbers reads as recall.
- How do you design a thread-safe, lock-free queue data structure for multi-threaded trade order dispatching?
- Design an object-oriented system for a real-time order execution engine handling thousands of incoming quotes per second.
- Walk through the architecture of a low-latency trade logging and auditing service that guarantees zero data loss during system crashes.
Languages, concurrency and fundamentals
Most of these questions have a definition-shaped answer and a mechanism-shaped one, and only the second scores. Take them down to what exists in memory: which objects are allocated, which names hold a reference to them, and what changes when a call returns. That reframing answers most of the family, because copying, mutation, lifetime and equality are questions about the object graph. What separates a strong answer is refusing to stop at the keyword. Naming Java pass-by-value is a fact; the answer is saying that the value copied is the reference, so reassigning a parameter is invisible to the caller while mutating the object it points at is not.
- How does the Java Virtual Machine (JVM) manage object allocation in heap memory, and how does garbage collection impact application latency?
- Explain the difference between virtual memory, physical memory layout, and process address spaces in C++.
Behavioural and engineering judgement
Ownership questions here usually point at something with no owner and no deadline: an alert people mute, a suite everyone reruns until it passes, a service whose on-call is a rota of volunteers. Nothing forces action, so what you did says how you spend discretionary time. The strongest answers are not the ones where you fixed everything. They are the ones where you sized the problem first, chose between fixing it, containing it and writing it down for whoever comes next, told someone who could have overruled you, and can still say why the other two options were worse.
- How do you handle ambiguous project requirements when building tools for traders or quantitative researchers?
Other topics
A large share of these are really questions about evidence: why something is slow, or how much of something you would need. Convert the prompt into a number or an observation. For diagnosis, name the measurement that would tell two candidate explanations apart and say what each predicts before you collect it. For sizing, carry the units through and stop at one significant figure. Listing everything that could plausibly be true is what the weak version looks like. What separates the strong one is a hypothesis, the observation that would falsify it, and what the arithmetic already rules out before any new data arrives.
- Walk through the TCP/IP 3-way handshake and explain how socket buffers and TCP window sizes affect network transmission delays.
Practice exercises with worked solutions
The prompts above tell you what gets asked.
Denormalise tenant onto revisions and backfill it live
resource_revision (revision_id, resource_id, version, actor_user_id, change_kind, patch, request_id, created_at) has 400M rows and no tenant column; tenant_id lives only on resource. Two reads need it: a tenant-scoped audit feed ordered by created_at DESC, and an offboarding purge. Both join back to resource today. Justify adding tenant_id to resource_revision against those two reads, name the anomaly the copy introduces and the constraint that prevents it, then give the ordered migration for a live table taking 1.2k writes/second — the lock each step takes, how the backfill is batched, and where each step stops being reversible. PostgreSQL 16.
How a strong candidate works it
- Justify from the access path rather than from taste. Without the column, the audit feed either scans resource_revision by created_at and discards other tenants' rows, or resolves the tenant's resource_ids first and probes with them — both proportional to the tenant's whole history rather than to one page. With (tenant_id, created_at DESC, revision_id DESC) it is a seek that stops at 50 rows, and the purge becomes a ranged delete instead of a join.
- Name the cost exactly: a second copy of a fact can disagree with the first. Make the disagreement unwritable rather than documented — add UNIQUE (resource_id, tenant_id) on resource so it can serve as a foreign-key target, then FOREIGN KEY (resource_id, tenant_id) REFERENCES resource (resource_id, tenant_id) on the revision table. A revision can then only ever carry its parent's tenant.
- Step one, expand: ALTER TABLE resource_revision ADD COLUMN tenant_id BIGINT NULL, with no default, so it is a catalogue change and no rewrite. It still needs ACCESS EXCLUSIVE for an instant, and that instant queues behind the longest open transaction on the table while every later query queues behind it — set lock_timeout to 2s and retry rather than wait.
- Step two, dual-write: deploy the writer that populates tenant_id on every new revision while reads still use the join. Reversible by redeploying the previous build, because nothing reads the column yet.
- Step three, backfill: batch by primary key rather than by created_at so the cursor is dense and resumable — UPDATE resource_revision rr SET tenant_id = r.tenant_id FROM resource r WHERE r.resource_id = rr.resource_id AND rr.revision_id > $1 AND rr.revision_id <= $1 + 5000 AND rr.tenant_id IS NULL — committing per batch and persisting the cursor. Throttle on replica replay lag and on dead-tuple count, since each batch writes 5,000 new row versions. Run the backfill before the index exists so those updates can stay HOT.
- Step four, index then enforce then contract: CREATE INDEX CONCURRENTLY (cannot run inside a transaction block, scans the table twice, waits on open transactions, and leaves an INVALID index to drop concurrently if it fails); ADD CONSTRAINT ... CHECK (tenant_id IS NOT NULL) NOT VALID, then VALIDATE CONSTRAINT, which takes only SHARE UPDATE EXCLUSIVE, after which SET NOT NULL uses the validated check instead of re-scanning on PostgreSQL 12 and later. Only then move the audit reads onto the column and, in a later deploy, delete the join path.
Follow-ups
- The backfill is half finished and a rollback is required. What state is the table in, and what does the previous build do with a half-populated column?
- How do you verify the backfill actually finished, given rows are still being inserted while it runs?
- A resource must now be movable between tenants. What does that do to the composite foreign key and to the revisions already written?
Explain why the owner filter ignores the listing index
The only index on resource is (tenant_id, status, updated_at DESC, resource_id DESC). A new endpoint returns one user's resources across all statuses, newest created first: WHERE tenant_id = $1 AND owner_user_id = $2 ORDER BY created_at DESC LIMIT 20. On a tenant with 2M rows it takes 900 ms and EXPLAIN shows a sort above a large scan. Explain precisely why the existing index cannot serve it, give the index that can, and state which of these the new index still will not help: owner_user_id alone across tenants; the same query ordered by updated_at. PostgreSQL 16.
How a strong candidate works it
- Separate the two jobs an index does. For filtering, a composite btree is seekable only on a left prefix, so with no predicate on status the scan can at best range over tenant_id and test owner_user_id per row; PostgreSQL 16 has no btree skip scan to jump the unconstrained column.
- For ordering, the index is sorted by (status, updated_at) within a tenant and not by created_at, so the LIMIT cannot stop early: every matching row is read and then sorted. That is the 'Sort Method: top-N heapsort' line, and it is why the plan reads 2M rows to answer with 20.
- Derive the replacement from the access path — equality, equality, then the ordering column: CREATE INDEX CONCURRENTLY ON resource (tenant_id, owner_user_id, created_at DESC). The scan seeks to the (tenant, owner) range and walks 20 entries in order, so the Sort node disappears along with the row-read.
- Treat INCLUDE (title, status) as conditional, not free. An index-only scan still visits the heap for any row whose page is not marked all-visible, so on a table taking 1.2k writes/second the win depends on autovacuum keeping the visibility map current, and the wider index costs more on every insert.
- Answer the two negatives explicitly. owner_user_id alone is not a left prefix of the new index, so it degrades to a full scan of the index at best. Ordered by updated_at, the query still seeks on the (tenant, owner) pair but must sort, because only created_at is ordered within that pair.
- Measure both sides with EXPLAIN (ANALYZE, BUFFERS) and compare estimated against actual rows at the lowest node — a 2M-versus-200 misestimate there is usually what chose the plan, and adding an index will not fix a statistics problem.
Expected result (25 min): With (tenant_id, owner_user_id, created_at DESC) the plan is an Index Scan with no Sort, roughly 20 rows and a handful of buffers, sub-millisecond. owner_user_id alone still scans; ORDER BY updated_at seeks on the pair and then sorts that owner's rows.
Check yourself
- Actual rows at the leaf node is on the order of the LIMIT, not the tenant's row count.
- No Sort node appears above the index scan.
- Buffers read stays constant as one owner's row count grows; if it grows with it, the ORDER BY is not being satisfied by the index.
- pg_stat_user_indexes shows idx_scan climbing on the new index and not on the listing index for this endpoint.
Follow-ups
- 90% of rows are status='active'. Would a partial index WHERE status = 'active' change your answer, and for which of the three queries?
- A dashboard runs this for 40 owners in one page load. What changes about the design?
- How do you roll this index out on a table taking 1.2k writes/second, and what does it cost on every insert from then on?
Find overlapping job attempts and peak concurrency from lease records
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.
How a strong candidate works it
- 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.
- State the clock caveat: started_at and finished_at are written by different workers, so under skew of a few hundred milliseconds an apparent overlap shorter than that bound is not evidence. Filter reported overlaps by a minimum duration, or prefer timestamps written by whichever component heartbeats the lease.
- Scale the sort rather than assuming it fits: the sweep emits two endpoints per attempt, so 50,000,000 records become 100,000,000 endpoints, and at roughly 24 bytes each, an 8-byte timestamp plus a 4-byte job_type plus a kind flag padded to alignment, that is about 2.4 GB of sort keys before any scratch space. Either push the ordering into the database behind an index on (job_type, started_at) or run an external merge sort in chunks; the overlap pass sorts n records rather than 2n, so it is the cheaper of the two.
Expected result (30 min): One global sort by (job_run_id, started_at) driving a running-maximum-end check for overlaps, plus a sweep over 2n endpoints with ends ordered before starts for per-job_type peak concurrency, both O(n log n), with half-open intervals, NULL finished_at falling back to lease_expires_at, and overlaps shorter than the clock-skew bound excluded.
Check yourself
- Two attempts touching at exactly the same instant report concurrency 1, not 2.
- A job with a single attempt never appears in the overlap output regardless of its lease.
- The reported peak timestamp for a job_type lies inside at least one of that type's intervals.
Follow-ups
- 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?
- How would you compute both answers incrementally as records arrive rather than in a daily batch?
Relay committed events to the log without gaps or reordering
outbox_event rows are written in the same transaction as the state change and carry aggregate_type, aggregate_id, aggregate_version, payload and status, with a partial index on (created_at, event_id) WHERE status = 'pending'. The relay publishes about 4k events/second to a partitioned append-only log keyed by aggregate_id, with one leader per partition range holding a lease. Consumers must never miss an event; they may see one twice. Design the claim-publish-mark loop, and state exactly what consumers observe when a leader's lease expires while it is mid-batch.
How a strong candidate works it
- Claim with SELECT ... WHERE status='pending' ORDER BY created_at, event_id LIMIT $batch FOR UPDATE SKIP LOCKED inside a transaction. SKIP LOCKED lets several relay workers share a range without serialising on each other's rows, and the partial index keeps the claim proportional to the backlog rather than to a table that is overwhelmingly published rows. At 4k/second a batch of 500 is eight claims per second, each an index scan of 500 entries.
- Publish before marking, never the reverse, and say why it is a choice. Marking first loses the event outright if the process dies in the gap, and the loss is silent - nothing remains to retry, and it surfaces later as a projection missing a row. Publishing first can repeat the event, and repetition is what every consumer is already built to survive. That single ordering is the whole at-least-once guarantee.
- Preserve the only ordering on offer. Partition by aggregate_id and never publish two events for one aggregate concurrently: claim in (created_at, event_id) order and publish sequentially within an aggregate, or hash aggregate_id to a worker slot. Order across aggregates is not available at any price here, which is why the event carries aggregate_version and the full fact rather than a delta - a consumer can then discard what it has already applied without coordinating with anyone.
- State the failover behaviour precisely, because it is the consistency-versus-availability decision in this design. A lease expires because the holder is slow, and no mechanism distinguishes that from dead, so for the length of the lease window two leaders can publish the same claimed batch. The system accepts duplicates to avoid stalling publication for every aggregate in the range whenever one worker pauses. Consumers deduplicate on (aggregate_id, aggregate_version) and drop anything at or below what they have applied.
- Bound the failure paths and pick the right alarm. A row that fails to publish increments attempts, records last_error, and moves to 'dead' after a limit so one poison payload cannot block the backlog behind it. Alert on the age of the oldest pending row, not on the relay's error rate: the failure worth catching is a relay reporting itself healthy while nothing is being published.
Expected result (25 min): A claim using FOR UPDATE SKIP LOCKED over the partial index in (created_at, event_id) order, publish to the aggregate_id partition, then mark published - in that order, accepting duplicates on lease expiry. Per-aggregate order is preserved by never publishing an aggregate concurrently; cross-aggregate order is explicitly not guaranteed. Failures increment attempts and terminate at 'dead'. The alarm is the age of the oldest pending row.
Check yourself
- Killing the relay mid-batch produces duplicates on restart and no gap.
- The claim query's plan uses the partial index and reads at most batch-size entries regardless of table size.
- Two relay workers on the same range never block on each other's rows.
- Events for one aggregate_id arrive in aggregate_version order, and no consumer relies on order across aggregates.
Follow-ups
- The relay is down 40 minutes and 9.6 million rows are pending. What does catch-up do to the primary, and what changes in the claim loop to survive it?
- A consumer insists it never received an event. Which single query settles whether the relay lost it, and what does each answer look like?
- Delivery is at-least-once. What would exactly-once require end to end, and why is that a property of the consumer rather than of the relay?
One customer endpoint stalls deliveries to every other destination
The egress service delivers about 1.5k webhooks/second across 40,000 destinations, with a per-destination concurrency cap of 4 and a 10-second connect-plus-read timeout. Throughput falls to 300/second, queue depth climbs, and p99 delivery latency for unaffected destinations goes from 200 ms to minutes, while the error rate barely moves. One tenant holds 900 destination rows whose URLs share a hostname that now answers in 9.5 seconds. Explain the mechanism with the arithmetic, then give the containment in the order you would apply it.
How a strong candidate works it
- Look at saturation before errors. A flat error rate with collapsing throughput says nothing is failing, things are waiting, so the first signal to pull is in-flight request count or pool wait time rather than the error counter. This is the distinction that decides the whole investigation.
- Group in-flight work by resolved host, not by destination id. The cap is keyed per destination row, so 900 rows sharing one hostname buy 3,600 concurrent slots against a single host, each held for 9.5 seconds. The bulkhead was never a bulkhead for that host, and grouping by the wrong dimension is why the dashboard looked healthy.
- Do the arithmetic in both directions. Required concurrency is arrival rate times latency, so 1.5k/second at 200 ms needs about 300 in flight, which is entirely consumed by 3,600 slow slots; conversely whatever concurrency is left sustains rate equals concurrency divided by 9.5 seconds, which is the 300/second you are seeing. Matching both numbers is what promotes this from a plausible story to the mechanism.
- Explain why the circuit breaker never helped. It opens on consecutive failures, and a 9.5-second response inside a 10-second timeout is a success. Slow is not failing, so an error-rate breaker cannot see this; you need a slow-call ratio, a deadline propagated from the caller's remaining budget, or a concurrency limiter.
- Contain in order: park the offending host so the shared pool drains, add a per-resolved-host concurrency cap alongside the per-destination one, give slow hosts their own queue so they cannot occupy the general pool, derive the timeout from the delivery deadline rather than a round number, and check the retry policy is not tripling load on a host that is already slow. Use backoff with full jitter so retries do not resynchronise on recovery.
- State the invariant you are restoring: one tenant's endpoints degrade only that tenant's deliveries. That is a property to load-test for, not to assume from a config value.
Follow-ups
- The host recovers to 80 ms. How long does the queue take to drain, and what does the drain do to the recovered host?
- Where should the 10-second timeout number actually come from?
- If that tenant had one destination row instead of 900, would the cap of 4 have saved you? What would you measure to be sure?
Unblock an engineer without taking the keyboard
A teammate has spent two days on a job handler that occasionally writes duplicate rows. They are certain the queue is delivering twice by mistake. You suspect a lease expiring under a slow handler, so the job is running concurrently with itself. Describe how you have unblocked someone in this position: what you asked before offering a hypothesis, what you showed them rather than told them, and what you left them owning. Then say what you would do if their theory turned out to be the right one.
How a strong candidate works it
- Ask before diagnosing, and ask for things answerable from data they already have: the attempt count on the job rows that produced duplicates, the handler's observed duration against its lease expiry, and whether the duplicate rows share a natural key that a unique constraint could have caught.
- Teach the shape rather than the answer. A lease cannot distinguish a dead worker from a slow one, so a handler that outruns its lease is running twice by design, and deploys deliver the other half by killing handlers mid-run on every rollout. Both of their candidate theories produce identical duplicate rows, which is why the evidence has to come from timings rather than from argument.
- Hand over a checklist they execute: a natural key on every write the handler performs so the second copy collides rather than appends, the record of intent written before any external effect, a lease heartbeat while running, and the metric that shows it working.
- Keep ownership with them deliberately. Pair on the first write, then step back; if you finish it yourself you have closed one ticket and left the same person stuck on the next redelivery.
- Close on the systemic gap that let two days pass, which is usually a missing dashboard for attempt counts or an undocumented at-least-once contract, and fix that rather than only the bug.
Follow-ups
- How would you distinguish a genuine double-delivery from a lease expiry using only the data already stored?
- Their handler calls an external endpoint before recording that it did. What do you tell them to change first?
- What do you do the third time the same person brings you the same class of bug?
Ship under a deadline and bound the debt you chose
You have four days to ship a tenant-facing listing endpoint. The version you would defend uses keyset pagination over (tenant_id, status, updated_at DESC, resource_id DESC); the version you can finish uses LIMIT/OFFSET with no matching index. Describe a deadline call you actually made of this shape: what you shipped, what you knowingly deferred, how you bounded the damage with a mechanism rather than an intention, and the specific numeric condition that would force the follow-up. Name who you told and where you wrote it down.
How a strong candidate works it
- Name the deferred failure precisely instead of calling it slow. OFFSET n makes the database produce and discard n rows, so cost grows with page depth; without an index matching the sort, every matching row is read and sorted before the limit applies; and rows inserted between two page fetches shift across the boundary so items are skipped or repeated with nothing in the response to signal it.
- Bound the blast radius with something mechanical rather than a promise: cap maximum page depth, cap page size, restrict the endpoint to one internal caller, or keep it behind a flag. State which failure each cap removes and which it leaves standing.
- Attach a number to the trigger and wire it to an alarm: the first tenant crossing N resources, or the endpoint's p99 crossing its share of the 400 ms budget, so the debt announces itself instead of waiting to be remembered.
- Write it where the next engineer looks, which is the code and the ticket, not a chat message: what was deferred, why, the cap, and the trigger.
- Report what actually happened in your real example, including the case where the trigger never fired and the debt was correctly never repaid.
Follow-ups
- At what page depth does the offset version breach your latency budget, given your page size and row counts?
- What breaks first when you switch to keyset pagination later, and what does a client holding an old page token see?
- Who would have overruled you if you had asked for two more days, and did you ask?
Where candidates lose points
Losing online-assessment time to the proctored platform instead of the problems
Candidates report Codility or HackerRank with video and microphone on. Beforehand, solve problems in a browser editor with the camera running and no other windows open, and make sure you can write sorting with a custom comparator, string splitting and your DP table setup without looking anything up. When a test fails, find the smallest failing input before you change code.
Answering a low-level question with the feature name instead of the mechanism
"Virtual functions give polymorphism" does not answer the reported vtable question. Explain where the vptr is stored, what the indirect call costs, and why it blocks inlining. Apply the same standard to virtual memory (page tables, address translation), JVM allocation (the allocation fast path, what a GC pause does to tail latency) and locks (what happens when a mutex is contended). Prepare each answer as mechanism, cost and example.
Submitting a take-home that passes but is not idiomatic, tested or extensible
Aim for idiomatic code, not only correct code. Follow the style conventions of the language you were given, separate parsing, core logic and output, test the boundaries, and keep the code modular enough that a live change for streaming input touches one place. Candidates describe a code review of the take-home during the Superday, so every shortcut is something you may have to explain.
Designing the order engine or trade logger without saying who owns shared state or when data is durable
For the reported execution engine and lock-free queue questions, state which thread owns each structure and how work moves between threads, whether through a single-writer queue, a lock or atomics. For the zero-data-loss logging question, say exactly when a record counts as written: appended, flushed to disk, and acknowledged. A design that skips these points does not answer the question as asked.
Describing your most complex project as team output with no decisions of your own
A reported behavioral question asks specifically about your own architectural choices and trade-offs. Choose two decisions you made, the alternatives you rejected, what each would have cost, and what you would change now. For the production-debugging question, give the symptom, the first thing you checked and why, and how you confirmed the fix.
Breadth then depth, for a generalist facing a mixed loop
Four days sample coding, design, fundamentals and the practical rounds at deliberately shallow depth, which is enough to surface the topics you did not know were in scope. That map, rather than a guess made on day one, decides where the last three days go.
Day 1 — Online assessment: arrays and dynamic programming
- Write a stock buy-and-sell DP from a blank file, stating the state, transition and base case before coding
- Record every standard-library call you had to look up and drill each one until you can write it from memory
Deliverable: Four solved problems, a written DP state definition, and a list of library calls you can now write without a reference.
Day 2 — Online assessment: trees, grids and a proctored mock
- Implement BST serialization and deserialization from scratch, plus an iterative DFS over a grid that computes connected-component bounds
- Run a timed mock in a browser editor with the camera on and no other windows open, covering one array, one DP and one graph problem
- For each failure, write the smallest failing input, the expected output and the actual output before fixing it
Deliverable: Working serialization and grid DFS code, and mock notes listing each failing input you isolated.
Day 3 — Phone screen: memory, runtimes and the language you will be tested in
- Write spoken answers to the reported questions on virtual memory and address spaces, vtables and their overhead, and JVM allocation and garbage collection, each covering mechanism, cost and example
- Review your chosen language's main containers (for C++, vector and unordered_map; for Java, ArrayList and HashMap): layout, growth, and the complexity of each operation
- Compare arrays, linked lists and hash maps on memory layout and cache behavior, one paragraph each
Deliverable: A one-page answer sheet for the low-level category that you can say aloud without reading.
Day 4 — Phone screen: concurrency, OS and networking
- Explain how a mutex, a condition variable and an atomic compare-and-swap work, and what a contended lock costs
- Answer the reported fundamentals questions: process scheduling and context-switch overhead, cache locality, the TCP three-way handshake, and how socket buffers and window sizes affect delay
- Rehearse a walkthrough of your most technical project that names two decisions you made and the trade-offs behind them
Deliverable: Written answers for the concurrency and networking questions, plus a rehearsed project walkthrough.
Day 5 — Take-home: build it to be changed
- Build a small data processor or execution-bot core in your primary language, keeping parsing, logic and output in separate modules
- Test empty, single, duplicate and malformed inputs, then break the logic on purpose and confirm at least one test fails
- Change the design live to accept streaming input instead of a batch, and note which files you had to touch
Deliverable: A small tested project with a README of assumptions, and a note on what the streaming change required.
Day 6 — Superday: system design
- Design the reported object-oriented order execution engine: classes, interfaces, threading model and error handling
- Explain the reported zero-data-loss trade logging service, stating exactly when a record counts as durable and which crash each step survives
- Sketch a thread-safe, lock-free queue for order dispatch and name the memory-ordering guarantees it relies on
- Work the guide's outbox relay design exercise for at-least-once delivery and deduplication
Deliverable: Three design sketches, each with a stated ownership model and durability point.
Day 7 — Superday rehearsal: back-to-back sessions and behavioral
- Run a coding problem, a design problem and a behavioral block back to back with no break
- Prepare stories for the reported behavioral prompts: most complex project, a production issue debugged under time pressure, ambiguous requirements from traders or researchers, and why principal trading
- Read the debugging drill on a slow endpoint stalling a shared pool and explain the saturation mechanism aloud
Deliverable: Notes from the back-to-back mock and four behavioral stories, each with a decision and an outcome.