cswork / libraryOpen practice workspace

Interview guides · DE Shaw

DE Shaw Software Engineer Interview Guide

API design practice for DE Shaw software engineer candidates: compatibility, versioning, and changes you cannot take back.

The 60-second brief

A Software Engineer at the DE Shaw group operates at the intersection of sophisticated finance and cutting-edge technology. In this role, you are not merely writing code; you are building the high-performance, resilient, and scalable systems that power one of the world's most successful quantitative investment firms. Your work directly impacts the firm's ability to process massive datasets, execute complex trading strategies, and maintain a competitive edge in global markets.

Prepare in one language you know well enough to debug in rather than the one you think reads best. Under a clock an unfamiliar language costs you standard-library lookups and iteration mechanics, and that time comes out of your thinking budget, not your typing budget.

Focus areas

  • Fold execution reports idempotently on venue exec id
  • Treat a send timeout as unknown, not failed
  • Replay recorded input through the same build

Overview

You will find yourself collaborating with some of the industry’s brightest minds, ranging from financial analysts to world-class researchers. The environment is intellectually rigorous, emphasizing precision, efficiency, and a deep understanding of algorithmic complexity. Whether you are working on core infrastructure or application-specific tools, you are expected to deliver solutions that are both technically elegant and mission-critical.

The culture at DE Shaw values intellectual curiosity and technical rigor. Be prepared to explain the "why" behind your design choices, as interviewers prioritize depth of understanding over surface-level knowledge.

The DE Shaw interview process

DE Shaw candidates report 2 rounds · ≈ 2-4 weeks. The stages below are what candidates describe, not a published process.

Online Assessment

What this round decides is narrow: whether you can produce code that runs and is correct on inputs nobody showed you. An elegant solution that does not compile scores below a plain one that does, so write a correct brute force first, say out loud that you know its cost, and improve it with the working version still on screen. What separates strong answers is who finds the broken case. Trace your own code against an empty input, a single element, and duplicate keys before you say you are finished, because being told is far more expensive than noticing.

What to demonstrate

  • Whether degenerate inputs get checked without being asked for: an empty collection, one element, every element equal, and the extreme value the input type allows
  • Whether the complexity you state matches the code you actually wrote, including a sort or a copy sitting inside a loop
  • Whether the finished answer is verified against the worked examples before you call it done, rather than assumed correct because the code reads correctly

How to prepare

  • Take five problems you have already solved and, without running anything, write down what each returns for empty input, a single element, and all-duplicates. Then run them and count how many you predicted wrong.
  • Drill the brute force as its own skill: on ten problems, write only the obviously-correct slow version and time how long it takes to get it passing. If that is more than a few minutes, that is what to practise, not the optimal version.
  • Add a fixed last step before you submit anything, reading only the loop bounds and the initial value of each accumulator, which is where most off-by-one errors live

Virtual Technical Rounds

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

Where candidates lose points

Representing prices and quantities as binary floating point.

IEEE-754 binary64 cannot represent 0.01 exactly, so a cost basis accumulated in doubles drifts, and an equality test against a tick boundary fails unpredictably. The fix is an integer at a fixed scale - a count of ticks, or a scaled integer at the instrument's price_scale - which also turns the tick-size check into a modulus rather than a tolerance comparison. The nuance that lets this trap survive code review: binary64 represents every integer exactly up to 2^53, about 9.0e15, so a scaled integer carried in a double is exact right up until it is not, and the first failure tends to be a large notional in a low-priced instrument, in production.

Allocating, taking a lock, or logging synchronously on the order path.

Each injects a delay whose magnitude depends on state you do not control: an allocation can fault in a new page, a lock can hand the core to another thread, a synchronous write can block on the filesystem. They also fail together, because all three are likeliest under load, which is when a burst is arriving. The hot path should preallocate, hand work to a logging thread over a single-producer single-consumer ring, and avoid any call that can enter the kernel. Verify by measurement rather than by reputation: a lock-free queue whose producer and consumer counters share a cache line can be slower than the mutex it replaced, because every update invalidates the other core's copy.

Never running a concrete value through the code

Trace one small input and one edge input by hand, index by index, out loud. Re-reading your own code catches design mistakes; walking a real value through it catches the off-by-one, the uninitialised accumulator and the loop that never advances.

Comparing floating-point values for equality, or holding money in them

Binary floating point cannot represent 0.1 exactly, so repeated addition drifts and an equality check fails on values that are mathematically equal. Store currency as integer minor units or a decimal type, and compare floats against a tolerance you chose for a stated reason.

Questions & practice

A sequence of characters is given; rearrange them so that no two consecutive characters are the same. (Approach: Frequency map + Priority Queue)

Medium · data structures and algorithms

Approach

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

Follow-up

  • What is the worst case, and how likely is it on real data?
  • Which test case would catch an off-by-one here?

Design data structures to handle high-frequency updates while maintaining search performance.

Medium · data structures and algorithms

Approach

  1. Walk one small example through your approach before writing the whole thing.
  2. Choose the data structure from the access pattern, not from familiarity.
  3. Restate the input: its shape, its size, and what is guaranteed about it.

Follow-up

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

Parse a tag-value wire message without allocating

Medium · parsing · zero allocation · hot path

A venue order-entry message is a byte buffer of tag=value fields separated by a 0x01 byte. The second field is a body length; the last is a modulo-256 checksum over every preceding byte, written as three ASCII digits. Some tags legitimately repeat inside repeating groups. Messages are at most 4 KiB and arrive at 10^6 per second. Extract eight named scalar tags as (offset, length) spans into a stack struct, and validate both the body length and the checksum, in one pass with zero allocations and no exceptions on the hot path. State the bytes touched per message and how you report a malformed message.

Approach

  1. Make one forward pass over the buffer that does three jobs at once: accumulate the checksum into a wrapping u8, parse each tag's ASCII digits into an integer until the '=', and record the value's start offset until the next 0x01. Bytes touched is exactly the message length once, which at 4 KiB and 10^6 messages per second is 4 GB/s of sequential reads and therefore the real ceiling on this design.
  2. Never materialise a value. Store (offset, length) pairs into a fixed-size struct on the stack, and parse integers directly out of the buffer when the field is consumed. A substring or a string_view constructed with a copy is the allocation that turns a 200 ns parse into a 2 us one under load.
  3. Dispatch on tag number with a small direct-indexed table for tags below 256 and a switch for the rest, rather than a hash map: eight tags of interest means the table is a branch-free load, and the map would add a hash and a probe per field.
  4. Handle repeating groups structurally, not by precedence. Record the offset at which the first group-count tag appears; a scalar capture is valid only before that point, and member fields after it belong to the group. Last-one-wins over a flat table is the failure this prevents, and it corrupts silently because the group's member tag is a perfectly legal tag number.
  5. Validate in the same pass: the body length counts the bytes from the byte after the body-length field's delimiter through the delimiter preceding the checksum tag, and the checksum is the running sum of every byte before the checksum field taken mod 256 and compared against the three digits. Both are cheap because the running sum already exists.
  6. Return a status enum, never throw. A malformed message on the decode path becomes a counter increment, a copy of the buffer into a slow-path ring for a logging thread, and a continue — throwing unwinds through the hot path and the cost lands on the next good message.

Worked solution (30 min)

  1. Construct a fixture message with the eight scalar tags, a repeating group whose member tag collides with one of them, a correct body length and a correct checksum; keep the raw bytes in a test constant.
  2. Compute the checksum two independent ways — a naive byte loop over the whole prefix, and the parser's running accumulator — and confirm they agree.
  3. Run the parser and assert each captured span resolves to the scalar value, not the group member's value.
  4. Mutate the fixture three ways: body length off by one, one checksum digit changed, and the final 0x01 removed; run each and record the status returned.
  5. Wrap the run in an allocation counter and a byte counter and run 10^6 iterations.

Expected result: The clean fixture returns status OK with eight spans whose resolved values match the scalar fields, and the colliding group member does not appear in any span. All three mutations return distinct non-OK statuses with no exception thrown and no partially populated struct handed onward. The allocation counter reads zero and the byte counter reads exactly message_length x iterations.

Verify it yourself

  • The two independent checksum computations agree on the clean fixture and disagree on the mutated one.
  • Allocation count is zero across 10^6 iterations, including on the malformed inputs where the error path runs.
  • Removing the repeating-group guard makes one span change value, which is the concrete demonstration of the trap.

Follow-up

  • The message contains a length-prefixed raw data field whose value may itself contain a 0x01 byte. What changes in the scan?
  • Where would SIMD actually help here, and where would it lose to the scalar loop?
  • Two venues use the same framing with different tag numbering. How do you keep one hot loop without a per-venue branch inside it?

Derive end-of-day positions from executions including trade corrections

Medium · event-sourcing · idempotency · aggregation · corrections

You have position_snapshot rows with snapshot_kind = 'sod_clearing' (account_id, instrument_id, business_date, net_qty) and execution_report (PRIMARY KEY (venue_mic, venue_exec_id), order_id, instrument_id, account_id, exec_type, last_qty, last_px, corrects_exec_id, source, venue_ts, received_ts). Write the query producing eod_derived net_qty per account and instrument for one business date. exec_type includes trade_correct and trade_cancel, which arrive as new rows referencing an earlier execution; the original row is retained. Side lives on order_event, not here. Then say why avg_cost_px cannot be computed in the same statement.

Approach

  1. Get side without fanning out: join each execution to the single order_event row with event_type = 'submit' for its order_id. Joining on order_id alone multiplies every fill by the number of lifecycle rows that order accumulated, which is five or more on a partially filled order.
  2. Reduce corrections to an anti-join instead of arithmetic. Exclude any execution whose (venue_mic, venue_exec_id) appears as the (venue_mic, corrects_exec_id) of a later row: a superseded fill then contributes nothing, a trade_cancel contributes nothing itself, and a trade_correct contributes its restated last_qty once. Match on venue_mic too, since venue_exec_id is only unique within a venue.
  3. Sum CASE WHEN side IN ('buy','buy_to_cover') THEN last_qty ELSE -last_qty END over exec_type IN ('partial_fill','fill','trade_correct'), grouped by (account_id, instrument_id), then FULL OUTER JOIN onto the sod_clearing rows with COALESCE on both sides so an instrument traded from flat and one closed to flat both appear.
  4. Scope the date by the venue's business date, not by received_ts::date in the server's timezone. A correction for yesterday arrives today and belongs to yesterday.
  5. Do not dedupe in the reader. The primary key already makes a resend from drop_copy or the clearing file a no-op at write time (ON CONFLICT DO NOTHING), so a DISTINCT in the query is either redundant or hiding a schema problem.
  6. Answer the avg_cost_px question precisely: average cost depends on the running position at the moment of each fill, because a reducing trade leaves it unchanged and realizes P&L while a sign flip resets it. That is a sequential fold with carry, not an aggregate. SUM(...) OVER (ORDER BY ...) gives running quantity but cannot carry the average, so it needs a recursive CTE or an application-side fold.

Follow-up

  • A resent report for an execution already inserted from the trading session arrives on the drop copy. What does your write path do, and what does the reader have to change?
  • Make this incremental so the intraday_derived row refreshes every few seconds without rescanning the day. What does the watermark have to be ordered by?
  • The derived total differs from eod_reconciled by 100 shares in one instrument. What do you do to the derived row?

Current order state from an append-only event log

Easy · window-functions · indexing · event-sourcing

order_event is append-only: event_id BIGINT PRIMARY KEY, order_id BIGINT, strategy_run_id UUID, seq_no INT (1-based and contiguous per order, UNIQUE (order_id, seq_no)), status_after, cum_qty, leaves_qty, received_ts TIMESTAMPTZ(6). The table holds 10^8 rows across 10^7 orders. Write the query returning current status_after, cum_qty and leaves_qty for every order in a supplied list of order_ids, then the variant returning current state for all orders of one strategy_run_id. State the index each needs, and why ordering by received_ts instead of seq_no is wrong.

Approach

  1. For the id list use SELECT DISTINCT ON (order_id) ... ORDER BY order_id, seq_no DESC, which takes the first row of each group and stops. The portable equivalent, ROW_NUMBER() OVER (PARTITION BY order_id ORDER BY seq_no DESC) = 1, ranks every row of every group before discarding all but one.
  2. Index (order_id, seq_no DESC). A btree can be scanned forwards or backwards as a whole, not per key, so the mixed-direction ORDER BY (order_id ASC, seq_no DESC) is not satisfied by (order_id, seq_no) and the planner inserts a sort.
  3. For the strategy_run_id variant the leading column changes to (strategy_run_id, order_id, seq_no DESC); otherwise the run's orders are found by a scan and grouped afterwards, which is the difference between 10^4 index descents and 10^8 row reads.
  4. Justify seq_no over received_ts concretely: received_ts is neither unique nor monotonic across reports. Two reports can share a microsecond, and a resend after a reconnect can arrive after a report it precedes, so MAX(received_ts) picks a non-deterministic row and can return an older state than the one the system acted on.
  5. Name the cost that motivates a maintained order_current table: this query is one index descent per order in scope, fine for a list and unacceptable for a dashboard refreshing every live order in a session. The trade-off is a second write on the order path and a crash window between the append and the update.

Worked solution (20 min)

  1. Seed one order with six events, giving two of them the same received_ts and inserting the final event with a lower event_id than its predecessor.
  2. Run the DISTINCT ON query and a MAX(received_ts) variant against that order and diff the two rows.
  3. EXPLAIN the query under both index shapes, (order_id, seq_no) and (order_id, seq_no DESC), and look for the sort node.
  4. Repeat for the strategy_run_id variant over a run holding 10^4 orders.

Expected result: One row per order_id carrying the highest-seq_no state. The MAX(received_ts) variant returns a different, older row for the order with the tied timestamp, and the DESC index removes the sort node from the plan.

Verify it yourself

  • The result row count equals the number of distinct order_ids requested.
  • For every returned row, seq_no equals MAX(seq_no) for that order.
  • With the DESC index in place the plan shows no sort node above the index scan.

Follow-up

  • How do you assert seq_no is contiguous per order, and what should the system do when it is not?
  • Write the query listing orders whose latest row violates cum_qty + leaves_qty = order_qty while the order is live.
  • How would you keep order_current correct if the writer can crash between appending the event and updating the summary row?

Implementing complex UI components from scratch.

Medium

Approach

  1. Clarify what is being asked and what a complete answer contains.
  2. Say what you would check first and why it is the highest-information step.
  3. Work from the requirement backwards to the design.

Follow-up

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

Handling asynchronous data flow within a component-based architecture.

Medium

Approach

  1. Say what you would check first and why it is the highest-information step.
  2. Work from the requirement backwards to the design.
  3. Clarify what is being asked and what a complete answer contains.

Follow-up

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

State management strategies for high-performance dashboards.

Medium

Approach

  1. Work from the requirement backwards to the design.
  2. Clarify what is being asked and what a complete answer contains.
  3. State your assumptions explicitly before working the problem.

Follow-up

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

Fold execution reports into positions exactly once

Medium · idempotency · event folding · deterministic ordering · watermarks

execution_report has PRIMARY KEY (venue_mic, venue_exec_id) and carries exec_type, last_qty, last_px, cum_qty, leaves_qty, corrects_exec_id, source ('trading_session', 'drop_copy' or 'clearing_file'), is_possible_duplicate, venue_ts, received_ts and applied_ts. position_snapshot holds one row per (account_id, instrument_id, business_date, snapshot_kind) with net_qty, avg_cost_px and fills_applied_through_exec_id. Cost basis is weighted average: an increasing fill re-weights avg_cost_px, a reducing fill leaves avg_cost_px unchanged and realizes the difference between last_px and avg_cost_px on the quantity closed (signed so that closing a long above its average and closing a short below its average are both gains), and a fill crossing through zero realizes against the whole old position and opens a new basis at that fill's price. Volume is 100,000 to 10,000,000 executions a day, the same execution legitimately arrives from all three sources, and drop copy can deliver one fill before the session delivers an earlier one. Design the fold: what makes reapplication a no-op, what order the cost fold replays in and what a row arriving out of that order costs, how trade corrections are represented, and how an intraday reader gets a position no more than a few seconds stale.

Approach

  1. Separate identity from arithmetic. Deduplicate at insert with INSERT ... ON CONFLICT (venue_mic, venue_exec_id) DO NOTHING so three sources carrying one execution produce one row, then fold rows rather than messages. That places exactly-once at the sink, where it can actually be enforced, instead of assuming it of the transport.
  2. Split the derived state by its algebra, because only part of it is order-free. net_qty is a sum of signed last_qty over a set of exec ids, so arrival order cannot change it. cum_qty and leaves_qty are the venue's statement about an order, taken from the report carrying that order's latest venue state and never accumulated locally; conflating the two is the classic overfill bug. avg_cost_px and realized_pnl are neither, they are a sequential fold with carry: a reducing fill realizes against the running average, an increasing fill re-weights it, and a fill through zero discards the basis entirely. The one order-free case is pure same-side accumulation, where the average collapses to sum(last_qty * last_px) / sum(last_qty).
  3. Give the cost fold a total order derived from the rows themselves: the venue's own monotonic execution sequence where it publishes one, otherwise (venue_ts, venue_mic, venue_exec_id), whose tail is the primary key so the order is total. The tiebreak inside one venue_ts is arbitrary but fixed, which is all reproducibility requires; it is not a claim about the venue's true matching sequence. The internal ingest sequence is an arrival artifact, the right resume watermark and the wrong fold order, because a session resend, or a drop-copy row that beats the session copy of an earlier fill, moves a reducing fill relative to the average it realizes against while leaving net_qty, the row count and every duplicate count intact.
  4. Treat a row that sorts below rows already folded as a rewind rather than an append: reopen that (account_id, instrument_id, business_date) from its opening snapshot and replay the day's rows for that key. Scoping the replay to the key bounds it at thousands of rows rather than the day's millions, and the rewind rate is worth counting, since a rising one is a feed or drop-copy problem rather than a fold problem. Trade corrections are the benign case, since trade_correct and trade_cancel arrive as new rows referencing corrects_exec_id with their own later venue_ts and therefore append in order. Fold them as a negating entry for the original plus, for a correction, the replacement, and never UPDATE the original row; it is the record of what the desk acted on at the time, and realized P&L attribution needs both sides.
  5. Fix the watermark and the read path separately. Resume from the internal monotonic ingest sequence assigned at insert, since venue_exec_id is an opaque vendor string with no ordering guarantee within a venue let alone across venues, and keep fills_applied_through_exec_id for audit. Serve intraday reads from the snapshot row rather than from execution history, so a read is a primary-key lookup instead of an O(n) fold. One writer per account partition keeps the fold single-threaded per row; if several writers can touch one position row, a check-then-act under READ COMMITTED reads a value that is stale before it acts, so use SELECT ... FOR UPDATE, a version-guarded conditional UPDATE, or SERIALIZABLE with a retry on serialization failure.

Worked solution (35 min)

  1. Create execution_report with its primary key and load a recorded stream of 50,000 reports covering 500 orders across 40 (account_id, instrument_id) pairs, including 2,000 deliberate duplicates, 50 trade_correct rows, and the two shapes the cost fold is sensitive to: at least 200 reducing fills and 20 fills that cross through zero.
  2. Implement apply(report) as dedup on (venue_mic, venue_exec_id) followed by a fold that replays in (venue_ts, venue_mic, venue_exec_id) order with avg_cost_px and realized_pnl in exact decimal, then run the stream three ways: in order, shuffled, and with every row duplicated.
  3. Compare net_qty, avg_cost_px, realized_pnl and per-order cum_qty across the three runs.
  4. Run the shuffled stream once more through a control fold that replays in ingest order instead, and diff its per-pair avg_cost_px and realized_pnl against the canonical result.
  5. Assert cum_qty + leaves_qty = order_qty on every live order_event row the fold produced.

Expected result: net_qty agrees across all three runs unconditionally, being a sum of signed last_qty over a set of exec ids. avg_cost_px and realized_pnl agree too, but for a reason worth naming rather than assuming: the duplicated run agrees because dedup leaves it the identical row set, and the shuffled run agrees because shuffling changes arrival while the fold sorts on (venue_ts, venue_mic, venue_exec_id). The step 4 control is what shows that sort is doing the work. Folding the same shuffled stream in ingest order reproduces net_qty exactly and disagrees on avg_cost_px and realized_pnl on every (account_id, instrument_id) pair holding at least one reducing fill or zero crossing, while pairs of pure same-side accumulation still agree. Both folds reconcile on row count, duplicate count and cum_qty + leaves_qty = order_qty, which is why this defect survives reconciliation and surfaces months later as a P&L break nobody can source. Per-order cum_qty equals the venue's stated value on the report carrying that order's latest state, not the sum of last_qty over messages received, and every corrected execution leaves the original row present with a negating entry beside it and no row updated in place.

Verify it yourself

  • A from-scratch fold of the whole day in canonical order reproduces the incremental fold's eod_derived rows exactly, avg_cost_px and realized_pnl included. That needs exact decimal arithmetic (PostgreSQL numeric, or scaled integers): binary floating point is deterministic under a fixed order but not associative, so any recompute that batches or regroups the same rows drifts from the incremental result.
  • Two independent folds of one row set, primary and replica or today's and next week's, agree on avg_cost_px, which is possible only if both take their order from the rows rather than from their own arrival.
  • No order has cum_qty greater than order_qty at any point in the history, not only at the end of it.
  • Each exec id contributes to the position arithmetic exactly once, provable by counting applied rows rather than by reading the fold.

Follow-up

  • The clearing file at T+1 says net_qty is 300 and the derived value is 295. Which row do you write, and which do you leave alone?
  • Primary and replica agree on net_qty for every account and disagree on avg_cost_px for three of them. What is the smallest difference between their inputs that explains that, and which side is wrong?
  • Two writers own the same account partition for 30 seconds during a failover. What breaks, and what does not?

Position fold consumer restarts on the same message

Easy · poison message · idempotency · dead letter queue

At 10:14 the position fold stopped advancing. The consumer crashes and restarts every 30 seconds on an identical stack trace, lag grows, and intraday positions are stale on every desk. The message at the head is an execution_report with exec_type='trade_correct' whose corrects_exec_id refers to an execution id the consumer has never seen. The consumer commits its offset only after a successful apply. Give an ordered checklist to confirm the diagnosis and a fix that neither drops the correction nor blocks the partition.

Approach

  1. Identify the exact message before theorising. Read the stuck offset and the message itself; a restart loop with an unchanging stack is the cheapest bug in the system to pin down and the one most often guessed at instead.
  2. Classify it. Malformed and never-processable is one class; well-formed but not yet resolvable is another. A trade_correct whose referent is absent is usually the second: the original execution may have arrived only on the drop copy, may belong to an earlier business date, or may simply be behind this message in another partition.
  3. Confirm by looking for the referent. Query execution_report on (venue_mic, venue_exec_id) for the corrects_exec_id across all three source values and the adjacent business dates. If it exists somewhere, the defect is a visibility or ordering assumption in the consumer, not the message.
  4. Clear the head-of-line block without losing the record. Park the message on a retry queue with bounded attempts and increasing delay, and on exhaustion move it to a dead letter queue that raises an alert - never a silent drop, because a correction is the venue restating a trade already booked into a position.
  5. Make redelivery cheap rather than rare. Apply is idempotent on (venue_mic, venue_exec_id) and the offset commits after apply, so reprocessing costs nothing; a correction is applied as a negating entry against the original rather than an edit, which is what the retained corrects_exec_id is for.
  6. Alert on the shape, not the symptom. Consumer lag alone says slow; lag with a flat committed offset and a rising restart count says poisoned, and those two want different responses from whoever is paged.

Follow-up

  • The referent genuinely does not exist because the pipeline lost the original fill. What now, and what does the position row look like in the meantime?
  • How many retries before the dead letter queue, and what makes that number defensible rather than arbitrary?
  • Why is applying a correction by updating the original row the wrong shape for this table?

Suggested practice checklist

Roughly ninety minutes on weeknights with one longer weekend block. The plan cuts scope rather than compressing everything, on the assumption that one thing finished per night beats four half-started.

Day 1 — Fix the scope and take a cold baseline

  • Read the role description and write the three things the loop will almost certainly test, then write an explicit not-doing list and keep it visible all week.
  • Take one twenty-five-minute coding problem and one fifteen-minute design prompt cold, and write the single sentence naming what blocked each, because those two sentences decide where the remaining evenings go.
  • Set the week's rule: one thing finished every night, including the night you only have forty minutes.

Deliverable: A one-page scope with a not-doing list and two cold attempts, each carrying one sentence on what blocked it.

Day 2 — One pattern, written three times from blank

  • Choose the single pattern most likely to appear in your loop and write it three times from an empty file rather than editing the previous attempt.
  • On the third pass, write the invariant as a comment before the loop body and the complexity before the first line of code.
  • Stop at ninety minutes even if the third version is imperfect, and write the one thing you would fix given another hour.

Deliverable: Three independent implementations of the same pattern plus a note on what changed between them.

Day 3 — One design, only to the depth you can defend

  • Take one system shape and go only as far as requirements, interface and data model, refusing to draw a box you could not survive a follow-up about.
  • Attach one number to each non-functional requirement, deriving it rather than asserting it, and write the assumption the number rests on.
  • Write the one tradeoff you are choosing against and the observation that would make you reverse it.

Deliverable: One design at interface-and-schema depth with derived numbers and one written reversible tradeoff.

Day 4 — Only the fundamentals you will have to defend

  • Write, in under two hundred words each, the answers to the two questions that follow almost any implementation: why this structure and not the obvious alternative, and what happens to this code at a hundred times the input.
  • Write what an index actually costs: faster lookups on the indexed columns against a write that now maintains a second structure, plus the cases where the planner declines to use it anyway, low selectivity, or a predicate wrapping the column in a function.
  • Delete any answer you cannot deliver aloud in under a minute, since an answer that needs reading is not an answer you have.

Deliverable: Three written answers, each under two hundred words and each timed aloud.

Day 5 — Your own work, timed

  • Write a ninety-second and a four-minute version of your main project and time both aloud rather than reading them.
  • Prepare the two follow-ups that always come: what you would do differently, and how you knew it worked.
  • Put one number in the first sentence and be ready to say exactly where it came from and what it excludes.

Deliverable: Two timed narratives with one defensible number in the opening line.

Day 6 — The one full rehearsal, in the weekend block

  • Run a sixty-minute mock covering a coding round and a design round in one sitting with no break, because sustained attention is the thing evenings have not trained.
  • Immediately afterwards, and before hearing any feedback, write the three moments you lost the thread.
  • Spend the rest of the block only on those three moments, and on nothing you merely feel shaky about.

Deliverable: Mock notes naming three failure moments with a specific fix written under each.

Day 7 — Taper

  • Write the twenty-minute warm-up you will actually do on the morning: one problem you can already solve from a blank file, one design you can narrate, and nothing you have never seen.
  • Re-read only your own notes from this week and open no new material.
  • Write the logistics down: the editor or shared document you will be working in, whether execution and lookups are permitted, and the sentence you will use when you do not know something.

Deliverable: A one-page card holding the design structure, the project numbers, and the logistics.

The estimate and the day you knew

A slipped date is only a bad story if you sat on it. What matters is what you believed when you gave the number, the signal that told you it was wrong, how many days passed before you said so, and what you cut rather than asking for more time. Scope you defended counts as much as scope you dropped.

Resolve a review disagreement over floating-point prices

Easy · fixed-point · code review · numeric correctness

A pull request stores avg_cost_px as a double and checks it against a tick boundary with a tolerance of 1e-9. The author notes that every test passes and the values are small. You think it should be an integer at the instrument's price_scale. Describe a code review disagreement you resolved. State the argument you made, the evidence you produced inside the review rather than after it, how long it took, and how it ended — including the case where you were the one who changed position.

Approach

  1. Lead with a reproduction, not a principle. Four lines that accumulate 0.01 a hundred times and print a value that is not 1.0 settle more than a paragraph, because IEEE-754 binary64 has no exact representation of 0.01.
  2. Explain the author's own evidence rather than dismissing it. Binary64 represents every integer exactly up to 2^53, about 9.0e15, so a scaled value carried in a double is exact until magnitudes grow — which is why the tests pass and why the first failure is a large notional in a low-priced instrument, in production, months later.
  3. Name the alternative and what it buys beyond exactness: an integer at the instrument's price_scale, with tick_size expressed at that same scale, turns the boundary check into px % tick_size == 0 and deletes the 1e-9 constant that nobody in the room can derive.
  4. Scope the change honestly instead of expanding the review. It is not one column — it is every boundary that reads it and a migration for rows already written. Say whether you asked for it in this pull request or filed it with an owner.
  5. State your stopping rule. A review is not a place to win: an unresolved correctness point goes to a third person or a written decision, not to another round of comments, and a strong answer names the round at which it escalates.

Follow-up

  • The author counters that NUMERIC in PostgreSQL is exact, so why not use that end to end?
  • Where in this system is a double still the right choice?
  • How would you detect the drift in rows already written before you migrate them?

Reverse a simulator fill model after live results diverged

Medium · simulation fidelity · queue position · reversal

Your backtest fills a passive order whenever the market trades at its price. Two strategies approved on that model reached production and filled at roughly a third of the simulated rate, turning a projected edge into a loss. Describe a decision you reversed. State what you originally believed and why it was reasonable, the evidence that changed your mind, what reversing cost — including work discarded and strategies withdrawn — and how you made the replacement trustworthy rather than merely newer. Say what you would have measured earlier to shorten the gap.

Approach

  1. State the original model's appeal honestly before demolishing it: it is cheap, it needs no order-level data, and it is close to right for aggressive orders. The reversal is about scope, not about anyone being foolish.
  2. Name the mechanism rather than the symptom. A passive order fills only once the quantity resting ahead of it at that price level has traded or cancelled. Touch-the-price assumes queue position zero — the most optimistic assumption available — and it is biased in exactly one direction, which is why the error never showed up as noise.
  3. Give the comparison you ran: the same recording through both models, reporting fill rate and per-fill P&L, and the ratio of simulated to live fills over the same period. One reproducible ratio on one recording is the evidence; the two withdrawn strategies are its consequence.
  4. Own the replacement's residual error. A queue model needs the quantity ahead, and at price-level depth you cannot observe cancellations ahead of you, so your estimate is itself wrong — state the direction of that error. A candidate who claims the new model is correct has repeated the original mistake in a more expensive form.
  5. Describe how you made the reversal stick: re-run the already-approved backlog through both models and publish the per-strategy delta, so the reversal arrives as a number every strategy owner can see rather than as a memo they can ignore.
  6. Close on the earlier measurement. The live-versus-simulated fill ratio is computable in the first days of any passive strategy; tracking it from day one turns a six-month surprise into a two-week correction.

Follow-up

  • Price-level data hides cancellations ahead of you. How wrong can your queue estimate be, and in which direction?
  • How do you stop the new model being tuned until it happens to reproduce the live result?
  • What does the reversal imply for strategies that were rejected under the old model?

Ship an order gateway with named, scheduled reconciliation debt

Medium · technical debt · reconciliation · release scoping

A venue connection must be live for a session start in three weeks. The order state machine, idempotent application on (venue_mic, venue_exec_id) and the pre-trade check are done. Drop-copy reconciliation and trade_correct / trade_cancel handling are not. Describe a time you shipped with known debt. State exactly what you cut, the detector you put in its place, the date the debt was scheduled for, who agreed to it, and what the debt actually cost before it was paid. Include the item you cut that you should not have.

Approach

  1. Split the cut list into two piles out loud: things that make the system slower to operate, and things that make it silently wrong. Only the first pile is negotiable, and saying so is the whole judgement being tested.
  2. Place trade corrections in the right pile. A fold that only adds quantities cannot represent a negating entry referencing an earlier execution at all, so cutting trade_correct is not an unhandled edge case — it is a position that is wrong with nothing raised. If you cut it, the compensating control is a break report that blocks the next session, not a log line.
  3. Attach a detector, a threshold and an owner to every item in the silently-wrong pile. Without drop-copy reconciliation the control is an end-of-day diff of derived against clearing positions, paging a named person on break_qty != 0, with a stated maximum position size until the real thing lands.
  4. Make the schedule concrete. The debt lands in a named release with a ticket, and the interim risk limit is what gets relaxed when it lands — which gives the desk a reason to care about the date instead of treating it as engineering's problem.
  5. Report what it actually cost: how many breaks per day, how much manual work, whether any of it reached a position, and which cut you would take back. The signal is your calibration, not your willingness to ship.

Follow-up

  • A trade correction arrives for a fill booked three sessions ago. What does your compensating control actually do with it?
  • The debt slipped twice. What changes on the second slip?
  • How would you scope this if the date were immovable and the desk wanted twice the size?