Interview guides · Jane Street
Jane Street Software Engineer Interview Guide
Jane Street Software Engineer interview guide: the 4 reported rounds, coding, class design, probability and debugging questions, and a 7-day plan.
Interviewing at Jane Street
- Practice bank: 26+ questions
- Rounds: 4
- Typical prep: 2–4 weeks
- Interview reports: 10
Interview rounds
| Round | Questions |
|---|---|
| HR Screen | 4 |
| Online Assessment | 1 |
| Technical Screen | 16 |
| Onsite | 5 |
How hard is the Jane Street Software Engineer interview?
| Difficulty | Share | Questions |
|---|---|---|
| Easy | 27% | 7 |
| Medium | 42% | 11 |
| Hard | 31% | 8 |
What Jane Street actually tests for
| Topic | Share | Questions |
|---|---|---|
| Coding & Algorithms | 54% | 14 |
| Statistics & Math | 15% | 4 |
| System Design | 12% | 3 |
| Software Engineering Fundamentals | 8% | 2 |
| Behavioral & Leadership | 4% | 1 |
| Data Manipulation (SQL/Python) | 4% | 1 |
| ML System Design | 4% | 1 |
The 60-second brief
Jane Street is a quantitative trading firm. Its software engineers build trading systems, execution engines, risk management tools, real-time data pipelines and internal tools for traders and researchers. OCaml is the firm's primary programming language. Reports say candidates are judged on problem-solving and computer science fundamentals, not on functional programming experience. The reported questions follow the same mix: practical coding that grows in stages, class design for stateful tools like editors and game boards, probability and combinatorics puzzles, and system design and debugging.
Jane Street candidates report 4 rounds · ≈ 3-5 weeks. The stages below are what candidates describe, not a published process.
What a Software Engineer does at Jane Street
OCaml is the firm's primary language. Reports say candidates may interview in a mainstream language such as Python, C++ or Java, and list prior functional programming experience as a nice-to-have that is taught on the job. The systems have to be correct and fast: a bug or a latency spike in a production trading loop can have direct financial consequences. That is why the reported interview advice keeps returning to readable code, precise data modelling and checking your own work before calling it done.
The reported questions are practical and often multi-part. A bracket validator later has to return exact positions. A run-length encoder later has to decode partial streams. A trade-volume aggregator later has to handle short sales and credit adjustments. Class-design questions model stateful tools: a game board where pieces enter from the bottom, a text editor buffer, a code-folding editor. Probability and combinatorics questions (a three-player dice game, palindromic permutations, conditional probability) sit alongside system design, SQL and debugging questions from the bank. Prepare to extend your own code under new requirements, not only to solve fresh problems.
The Jane Street interview process
Jane Street candidates report 4 rounds · ≈ 3-5 weeks. The stages below are what candidates describe, not a published process.
Online Assessment
Candidates report that the process opens with an online assessment or take-home task. It may be a coding assessment or a data analysis task. Some tracks report a data challenge in Excel that tests how quickly you can work with a raw dataset. Ask your recruiter which format you will get, because the preparation differs. A coding assessment rewards a solution tested against inputs the examples do not show. A spreadsheet task rewards formulas that are correct, easy to trace and quick to build.
What it evaluates
- In a coding assessment, whether the solution handles cases beyond the worked examples, such as empty input, a single element and repeated values
- In a data analysis task, whether you can clean, look up and aggregate a raw dataset with formulas you can explain and audit
- Whether you finish something correct rather than leaving an ambitious approach half-built
How to prepare
- Before each practice solution, write a small test harness that runs the stated examples plus empty and single-element inputs and prints expected against actual
- Practise the bank question on computing haircuts with Excel formulas, plus lookups, conditional sums, absolute versus relative references and pivot tables, until you can build them without searching
- Confirm the format with your recruiter and put your practice into that format
Virtual Technical Rounds
Reports describe one or two virtual technical rounds of live coding and quantitative problem-solving, held in a shared editor such as CoderPad or over Zoom with a current engineer. The guide's reported coding questions, whichever round they come from, are practical and multi-part: bracket parsing that later returns positions, a run-length encoder that later handles partial streams, and trade aggregation that later handles short sales. The reported quantitative questions cover probability, combinatorics and expected value. Treat the session as pair programming. Say your plan, write readable code, and trace it by hand before calling it finished.
What it evaluates
- Whether you restate the problem and name an approach and its cost before typing
- Whether part one leaves behind state that makes the next requirement cheap to add, rather than forcing a rewrite
- Whether a probability problem is set up precisely, with the sample space and any tie or reset rule stated, before any calculation
- Whether you test a hint against a concrete input and act on the result, rather than arguing past it
How to prepare
- Solve the reported bracket, run-length encoding and trade-processing questions in your most fluent language, in a plain editor, narrating as you go
- Work three probability questions out loud, including the three-player dice game and the bank's conditional probability with balls, and check each answer with a symmetry, sums-to-one or enumeration test where the rules make that test valid
- Ask a partner to add a requirement halfway through a problem, and practise extending your code without breaking part one's tests
Onsite Interview Day
Candidates who pass the virtual rounds report a full-day onsite, called a Superday, or a virtual equivalent, of three to five technical rounds back to back. The rounds cover practical coding, class design, systems concepts and real-time troubleshooting. The risk is fatigue, and carrying one room's mode into the next: over-designing a coding problem, or coding a class-design question before the interface is agreed. Reset your opening in every room: restate the problem, ask for constraints, and say the plan before writing anything.
What it evaluates
- Whether your opening habits (restating, clarifying, planning aloud) still hold in the later rounds of the day
- Whether a class-design answer starts from an agreed interface and representation before any implementation
- Whether code is checked by tracing a small example before you declare it done
How to prepare
- Book three to five mocks back to back on one day, mixing coding, class design, probability and debugging, and note where your habits slipped
- For each reported class-design question, write the public method signatures and one small example state before any implementation
- Keep a short card with your opening steps for each question type and read it before every mock
Technical Rounds
Reports say the onsite technical rounds cover coding, system design and deep-dive debugging. For the debugging side, practise tasks such as searching logs on a virtual machine, or working in a terminal with failing service logs to isolate a memory leak or a network timeout. For the design side, practise bank questions such as distributed system design, order book APIs, processing one million quote updates per second, and database design for a market. In both kinds of problem, state your assumptions and your evidence before you act.
What it evaluates
- Whether a debugging session moves from observation to one testable hypothesis to a confirming check before anything is changed
- Whether a design answer settles requirements, throughput and failure cases before choosing components
- Whether trade-offs such as latency against consistency are explained in terms of the specific problem rather than in general
How to prepare
- Work the guide's cache-stampede debugging drill and write the ordered checks that separate a genuine demand increase from a retry storm
- Do the worked design exercise on recovering an order session that dropped mid-fill, and the worked SQL exercise on join fan-out
- Practise reading a real service log from the command line with grep, tail, sort and uniq, and say what each command rules in or out
Questions Jane Street candidates report
Data structures and algorithms
Every fast solution here throws work away, and the claim you have to defend is that the discarded work could not have held the answer. A two-pointer walk over sorted values advances the left index only because every remaining pair using that element is also too small. A monotonic deque drops an element because a larger one sits to its right and will stay in the window longer. Say that sentence out loud. A weak answer names the technique and shows it passing the given examples, which is indistinguishable from a wrong technique that happens to pass the given examples.
- Parentheses tracking & string manipulation: Write a robust parser to validate nested brackets, then refactor it to return exact positions for inline string edits.
- Run-Length Encoding (RLE) utility: Implement a stream encoder and decoder using run-length encoding principles, building additional features to handle partial streams and data transformations.
- Trade transaction processing: Given a log of execution trades, write a function to aggregate trade volume and retrieve the top trades, extending the solution to correctly process short sales and credit adjustments.
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.
- Implement a game board simulator: Build a class representing a 2D grid game where pieces dropped from the bottom shift existing pieces upward, with validation logic to detect matching sequences along rows and columns.
- Visual editor state tracking: Design a simple parenthesis-matching structure, then extend it to store structural metadata to render editor visual cues.
- Design a interactive text editor buffer: Implement a backend class for a simple code editor supporting text insertion, line-by-line navigation, and block selection expansion/shrink.
Practice exercises with worked solutions
The prompts above tell you what gets asked.
Check-then-act on a position limit under concurrent submits
A pre-trade check reads the current net position for (account_id, instrument_id) from position_snapshot where snapshot_kind = 'intraday_derived', compares it against risk_limit.limit_value for limit_type = 'max_position_qty', and if the order fits, inserts an order_event row and updates that same snapshot row. Two orders for the same account and instrument are submitted concurrently under READ COMMITTED. Name the anomaly precisely and say what separates it from write skew, say whether PostgreSQL's REPEATABLE READ prevents it and under exactly what precondition, and give three implementations that hold the cap, with the cost of each and what happens when the snapshot row does not yet exist.
How a strong candidate works it
- Pin the interleaving before naming anything. Both transactions read the same position_snapshot row for (account_id, instrument_id): net_qty = 900 against a cap of 1000, both find 900 + 80 inside the cap, both proceed, and both then write that same row. That makes it a read-modify-write conflict on a single row, not write skew. Write skew requires the two transactions to write rows that the other's check did not read, which is precisely not the case here, as Implementation B's conditional UPDATE targeting the very row the check read makes obvious.
- Say which failure the SET clause buys. SET net_qty = 980, the value the application computed from its stale read, is a lost update: the second write overwrites the first, the row settles at 980, and the ledger now disagrees with the 1060 the venue actually filled. SET net_qty = net_qty + 80 loses nothing arithmetically and settles at 1060, over the cap and faithfully recorded. Both admitted two orders against one reading of the headroom.
- READ COMMITTED allows it. The second UPDATE blocks on the row lock, and once the first commits it re-evaluates its WHERE clause against the newly committed row version and proceeds. The write lands against fresh data; nothing re-checks the decision that authorised it.
- REPEATABLE READ does prevent this interleaving, and the precondition is the whole answer. PostgreSQL's REPEATABLE READ is snapshot isolation: a transaction that updates a row which a concurrent transaction committed after its snapshot is aborted with SQLSTATE 40001 rather than let through, so the second submit fails and a replay of the whole transaction re-reads 980 and rejects the order. That holds only because both transactions write the same row. Derive the position from a SUM over inserted order rows instead, so each transaction only appends and neither writes what the other read, and the identical check becomes genuine write skew, which REPEATABLE READ permits and only SERIALIZABLE catches.
- Implementation A, serialize on one row: SELECT ... FOR UPDATE the (account_id, instrument_id, business_date, 'intraday_derived') row inside the transaction. Correct only if every writer takes the same lock, and it has a hole worth naming: FOR UPDATE matching zero rows locks nothing, so the first order of the day in an instrument needs INSERT ... ON CONFLICT to materialize the row, or a lock on a surrogate such as pg_advisory_xact_lock over a hash of the pair, remembering that a hash collision merely over-serializes two unrelated pairs rather than letting either through. Cost: every order in a hot instrument serializes behind one row.
- Implementation B, collapse the check and the act into one statement: UPDATE position_snapshot SET pending_qty = pending_qty + :qty WHERE ... AND pending_qty + :qty <= :limit_value, and treat zero affected rows as the rejection. At READ COMMITTED an UPDATE that blocks on the row lock re-tests its WHERE against the version the other transaction committed, so the predicate is evaluated against fresh data with no window between reading and acting, and the second order is rejected with zero rows rather than admitted. Note the level interaction before deploying it: the same statement at REPEATABLE READ or SERIALIZABLE raises 40001 instead of quietly rejecting, so a retry loop is mandatory there rather than optional.
- Implementation C, SERIALIZABLE: serializable snapshot isolation tracks read-write dependencies rather than row versions, so it covers both shapes, the single-row conflict here that REPEATABLE READ already catches and the append-only variant it does not. It aborts with SQLSTATE 40001 and does not block. The retry has to replay the transaction from BEGIN including the read the decision rested on, because an aborted transaction rejects every further command with 25P02, so retrying just the failed statement is not a thing that exists. Throughput falls as contention on the pair rises, and an unbounded retry loop turns a hot instrument into a livelock.
- Then say what a trading system actually does, because the interviewer is waiting for it: the check is not a database round trip at all. It runs in process on a single-writer order path against a preloaded, versioned limit snapshot, because one round trip costs more than the entire tick-to-trade budget. The rows are the audit record, and the check records which limit_version it evaluated against so a rejection is explainable months later.
Follow-ups
- A limit is tightened at 10:00:00.000 while an order approved at 09:59:59.999 is in flight. What does the venue see, and what does your ledger record?
- Your in-process check is the authority. How do you bound its divergence from the database, and what happens when they disagree?
- Two order gateway processes serve the same account. What stops each from believing it owns the whole position?
A desk report that silently multiplies filled quantity
A report joins order_event (many rows per order_id: submit, ack, each partial_fill, fill), execution_report (one row per venue execution, keyed (venue_mic, venue_exec_id)) and risk_limit (PRIMARY KEY (limit_id, limit_version), one row per version per effective window) to show, per strategy_run_id, filled quantity, filled notional, and the max_order_notional in force. Filled quantity comes back roughly five times too large and notional shifts run to run. Identify both fan-outs, give the diagnostic that proves each one, and rewrite the query so the totals are correct.
How a strong candidate works it
- Name the mechanism rather than the symptom: an inner join multiplies rows, and SUM over a multiplied row set multiplies the measure. Each execution matches every lifecycle row of its order, and each limit scope matches every stored version, so the two factors compound.
- Prove each fan-out with a cardinality probe on the key alone, before touching the aggregate: GROUP BY order_id HAVING count() > 1 on order_event, and GROUP BY limit_id HAVING count() > 1 on risk_limit. Then compare COUNT() of the joined set against COUNT() of execution_report restricted the same way; the ratio is the multiplier.
- Fix the execution side by aggregating before joining: a subquery summing last_qty and the notional per order_id, joined one-to-one against a genuine one-row-per-order source such as the submit event, instead of joining the event log directly.
- Fix the limit side with a LATERAL that picks the one version in force: effective_from <= the order's sent_ts AND (effective_to IS NULL OR effective_to > sent_ts) ORDER BY effective_from DESC LIMIT 1. Picking by MAX(limit_version) is a different answer and usually the wrong one, because the newest version may not have been in force when the order was sent.
- Scale notional correctly while you are in there: last_px is an integer at the instrument's price_scale and derivatives carry a contract_multiplier, so notional is last_qty * last_px * contract_multiplier / 10^price_scale, with both attributes resolved from the instrument version as of the execution.
- Reject the reflex fixes explicitly: SELECT DISTINCT and SUM(DISTINCT last_qty) both change the answer by collapsing two legitimately equal fills into one.
Expected result (30 min): Filled quantity per strategy_run_id equal to the sum of last_qty over surviving executions for that run, a limit column showing the version whose effective window contains the order's sent_ts, and a result whose row count equals the number of distinct strategy_run_ids in scope.
Check yourself
- Result row count equals COUNT(DISTINCT strategy_run_id) over the same filter.
- Filled quantity matches a standalone SUM(last_qty) over execution_report for the same run.
- Changing the number of ack rows on an order changes no total in the report.
Follow-ups
- A LEFT JOIN to risk_limit would keep orders that matched no limit. What does that do to your totals against an inner join, and which do you actually want?
- How would you catch this class of bug automatically before the report ships to a desk?
- Which of these joins would you push into a materialized view, and what event invalidates it?
Resolve position identity across a corporate action graph
Corporate actions are edges (from_instrument_id, to_instrument_id, action_type in symbol_change, merger, spinoff, expiry_roll, ratio_num, ratio_den, effective_ts) — up to 2 x 10^6 edges over 10^6 instruments. Given a position in instrument I held at t0, return the set of (instrument_id, exact rational multiplier) that it becomes at t1 > t0, following only edges whose effective_ts lies in (t0, t1]. Mergers give a node in-degree above one; spinoffs give out-degree above one. Detect cycles and report them as data errors rather than breaking them. Give both the per-query and the batch complexity.
How a strong candidate works it
- Start from what the shape of the answer has to be. A spinoff makes out-degree greater than one, so a position maps to a set, not to a single successor; a merger makes in-degree greater than one, so the reverse map is not a function. Anything that collapses each instrument to one representative — union-find being the usual reflex — cannot express either, and also destroys the as-of property that makes the query meaningful.
- Filter the edge set to effective_ts in (t0, t1] and traverse forward from I with DFS, carrying an exact rational multiplier along each path as a reduced (num, den) pair. Multiply at each hop, run gcd immediately to keep the components small, and use checked 128-bit multiplication so a long chain fails loudly rather than wrapping.
- Accumulate at the terminals by summing multipliers across distinct paths that reach the same instrument, because a spinoff that later re-merges into its parent genuinely contributes twice. Summing rationals means a common denominator, then a gcd reduction again.
- Run the cycle check before the path-carrying traversal, not after it. The DFS above carries a multiplier down every distinct path and so does not terminate on a cyclic subgraph at all; the traversal's termination is a precondition that Kahn establishes, not a property the traversal has on its own.
- Complexity: a single query is O(V + E) in the worst case. For a batch — end-of-day, every position in the book — sort nodes by effective_ts to get a topological order of the filtered subgraph and memoise the resolution per (node, t1), which makes the whole batch O(V + E) once instead of O(positions x (V + E)).
- Detect cycles with Kahn's algorithm on the filtered subgraph: after the queue drains, the residual set is exactly the nodes on or downstream of a cycle. Run Tarjan on the residual to name the strongly connected components of size above one, report them with the offending edges and exit non-zero. Time is monotone along real corporate actions, so a cycle is always a vendor or load error; deleting an edge to make the traversal terminate hides the error and leaves positions mapped to the wrong instrument.
- Keep ratios rational end to end. A 1/3 hop followed by a 3/1 hop — a consolidation and then a re-split, through distinct instruments — must compose to exactly 1/1, which it does with reduced rationals and does not with binary64, where the round trip lands near 0.9999999999999998 and a position quantity is then off by a share after rounding.
Expected result (40 min): On the three-edge fixture, 1000 units of A resolve to {C: 400, D: 250} — C via 1/1 then 2/5, D via 1/4 — with multipliers held as 2/5 and 1/4 rather than 0.4 and 0.25. Adding the A -> E -> F chain makes the set {C: 400, D: 250, F: 1000}, since 1/3 then 3/1 composes to exactly 1/1. With the C -> A edge present, Kahn's residual is {A, B, C, D, E, F} — the cycle plus everything reachable from it — and Tarjan names the one strongly connected component of size above one, {A, B, C}; the job reports it with the offending edges and exits non-zero rather than looping.
Check yourself
- The 1/3 then 3/1 composition down A -> E -> F equals 1/1 exactly after gcd reduction; the same computation in binary64 does not.
- The cycle fixture terminates and names the component, rather than either hanging or silently dropping an edge.
- Removing the spinoff edge drops D from the result and leaves C at 2/5 and F at 1/1 untouched, confirming the paths are accumulated independently.
Follow-ups
- A vendor correction arrives at 18:00 changing yesterday's merger ratio. What do you recompute, and what does that do to already-published positions?
- A cash-in-lieu component means the mapping is not purely quantity-to-quantity. Where does that land in the model?
- How do you make this resolution replayable, so a backtest run in six months resolves the same identity the live session did?
Recover an order session that dropped mid-fill
A venue session drops at 10:14:03 with three orders live: one submitted 8 ms earlier with no ack, one partially filled at 300 of 1,000, and one with a cancel request outstanding. The session returns 400 ms later and the venue replays execution reports it may already have delivered. An independent drop-copy stream is available. Design the recovery: what the gateway is entitled to believe about each order, what it may send while state is unknown, how replayed reports are applied, and when strategies may quote again. Name the trade-off you are making and what it costs.
How a strong candidate works it
- Classify by what is known rather than by the local status field. The unacked submit is ambiguous in both directions: the venue may hold it, may have filled it, may never have seen it. The partial fill has a known lower bound of 300 on cum_qty and an unknown upper bound. The outstanding cancel may have been applied or may have been beaten by a fill already matched.
- Resolve by asking, never by resending. On reconnect issue an order status request per live order, or a mass status request, and read the drop copy as an independent witness of the same facts. The client order id was persisted before the send precisely so the venue can be asked about it; resending the submit is the single action that converts the ambiguity into a real duplicate position.
- Apply the replayed reports through the same idempotent path as live ones: deduplicate on (venue_mic, venue_exec_id) and take the venue's cum_qty as authoritative over anything accumulated locally. Expect is_possible_duplicate to be set on some replayed reports and not others; the flag is a hint from the session layer, not the mechanism that makes reapplication safe.
- Make the consistency-versus-availability call and say what it costs. While any order's state is unknown the position is unknown, so every position-dependent risk check is evaluating a number that may be wrong. Permit risk-reducing actions, refuse new risk, and put a deadline on it: if status has not resolved within a stated number of seconds, escalate to a human and use the venue's cancel-on-disconnect if it offers one. The price is going dark for the 400 ms plus resolution time, and the thing bought is never hedging a position that does not exist.
- Close the loop afterwards. Reconcile the recovered state against the drop copy immediately and against clearing at end of day; a fill that appears only on the replayed stream and never in the status response is exactly the discrepancy position_snapshot's break column exists to surface rather than absorb.
Expected result (35 min): In every run the order's cum_qty after recovery equals the venue's stated value, never exceeds order_qty, and each order enters a terminal state exactly once. Replay ordering does not change the final state. The gateway sends no new order between the drop and status resolution and never resends the ambiguous submit. On the filled branch the position moves and the strategy is informed before it is permitted to quote again.
Check yourself
- Replaying the recorded session twice produces identical final state and an identical outbound message sequence, byte for byte.
- No outbound order message exists in the recording while any order was in an unknown state.
- Every execution applied during recovery appears exactly once in the position arithmetic, counted from the ledger rather than asserted.
Follow-ups
- The status response says filled 500, the last live report said 300, and 300 has already been hedged. What is the sequence of actions now?
- Cancel-on-disconnect is available on this venue. What does turning it on cost you on a day with a two-second network blip?
- How do you test this without a venue, and what is the minimum fidelity the fake needs to be worth trusting?
Instrument cache flush degrades every book at once
At 13:05 a vendor correction reaches the reference service. Within two seconds its request rate rises forty-fold, p99 goes from 3 ms to 2 s, callers time out and retry, and the market data gateway marks books degraded because it cannot resolve symbology. Every service caches instrument_version with a five-minute TTL and flushes the entire cache on a correction notice. Give an ordered checklist that distinguishes a capacity problem from a stampede, and a fix that preserves as-of resolution for replay.
How a strong candidate works it
- Decide whether demand rose or supply fell. Compare distinct keys requested against total requests through the spike window. Forty times the requests over roughly the same key set is a stampede compounded by retries; forty times the distinct keys is genuine new demand and wants a different fix entirely.
- Check whether misses are correlated across processes. A global flush makes every instance miss the same key in the same instant, so the service receives one request per instance per key rather than one request per key. A uniform TTL does the same thing more slowly by aligning expiry across instances that started together.
- Quantify what the callers add before touching the server. A client that retries a timeout without backoff and without jitter multiplies an overload it is already inside; count retries separately from first attempts and state the amplification factor.
- Narrow the invalidation. A correction affects specific instrument_ids, and flushing discards 10^4 to 10^6 unrelated entries to fix a handful. Invalidate by key, which requires the notice to carry the affected instrument_id list rather than being a bare signal.
- Coalesce and stagger. One in-flight fetch per key per process with concurrent callers awaiting that single fetch bounds fan-out to the process count; TTL jitter stops survivors re-aligning; a bounded retry budget with backoff stops the client side recreating the burst.
- Handle the degraded case honestly. Serving a stale definition is only acceptable where the caller records which version it served, because a replay must resolve symbology exactly as the live session did; where it cannot, the correct behaviour is the one the gateway already took - mark the book degraded rather than price off a guess.
Follow-ups
- Stale-while-revalidate would have kept the gateway quoting through this. When is that the right call, and when does it become a correctness bug?
- The correction notice is a topic every service subscribes to. What would you change about the notice itself?
- What signal would have made this visible as a design flaw before it was an incident?
Estimate a kernel-bypass migration you have never attempted
Leadership asks how long it would take to move the order path off the conventional kernel networking stack, where it sits in the tens of microseconds, onto a kernel-bypass stack targeting single-digit microseconds. You have never done this migration. Give an estimate you are prepared to defend: the decomposition, the range and what drives its width, the spike that would narrow it most, and the conditions under which you would recommend not doing it at all. State what you would refuse to put a number on until the spike is finished.
How a strong candidate works it
- Refuse the single number first and say what you are giving instead: a range with the driver of its width named. The width is the information the requester actually needs, and handing over a point estimate destroys it.
- Decompose by uncertainty, not by component. The transport swap is well-bounded. What is not is everything currently leaning on kernel facilities: packet capture feeding the recording, session recovery on reconnect, failover between redundant lines, the timestamping source, and core pinning and isolation. Mark each item done-before or never-done and estimate only the first group directly.
- Attack the premise before estimating the work. Profile where the tens of microseconds actually sit; if a meaningful share is allocation, a lock, a synchronous log write or a page fault, the migration buys much less than the headline and the cheaper work comes first. State that as a decision gate with a threshold, not as a caveat at the end.
- Specify the spike as the narrowest experiment that collapses the widest uncertainty: one instrument, one venue session, send and receive only, tick-to-trade p99.9 measured against the current path on the same open-loop harness, with a fixed time box and a written question it must answer.
- State the costs that persist after delivery, because that is the part usually missing. Taking the network out of the kernel also takes it out of the kernel's tooling, so capture, counters and the existing packet recording need replacements, and a core is now permanently dedicated. Those are recurring costs against a one-time latency gain.
- Name the conditions for not doing it and what you will not estimate yet: if the strategies are not latency-sensitive at the margin, or the venue queue rather than your stack is the binding constraint, the answer is no — and the recovery and capture rework stays unestimated until the spike says what the new stack actually provides.
Follow-ups
- Give me one number anyway, with the probability you attach to it.
- The spike shows 4 microseconds where you projected 9. What do you do with the estimate?
- What would you cut to get half the benefit in a quarter of the time?
Retract a latency result you had already published
You reported that a change cut p99 tick-to-trade by 40 percent, and the figure is now in a deck. A colleague shows that your harness sent the next request only after the previous reply arrived, so it stopped issuing load whenever the system stalled. Describe a time you retracted a result. State how you confirmed the error, who you told and in what order, what the corrected number was and how it was measured, and what you changed about how results get published so the next one is checkable by someone else.
How a strong candidate works it
- Confirm before announcing, but time-box the confirmation. Re-run under an open-loop generator that holds the intended send schedule and records latency from intended send time rather than actual send time, then compare it against the closed-loop run on the same build.
- Explain the mechanism precisely enough that the retraction is credible: a closed-loop harness never issues the requests that would have arrived during a stall, so the stall is sampled once instead of at the rate it would really have faced. That is coordinated omission, and it can understate the tail by an order of magnitude — so the correction is usually not a small adjustment to the same conclusion.
- Tell the people acting on the number before the people judging you, and say which decisions were made on it. If work was scheduled or descoped because of the figure, that belongs in the first sentence of the retraction, not the last.
- Give the corrected figure with its method attached: percentiles from a high-dynamic-range histogram, the load pattern, and the utilisation it was taken at. A tail number without a utilisation is not a number, because waiting time rises sharply as utilisation approaches one and real market data bursts harder than the queueing models that describe it.
- Separate what still holds from what does not. The change may remain a genuine improvement of smaller size, and saying so is part of being accurate — over-retracting is a failure of accuracy in the other direction.
- Change the process rather than only the number: require the harness type and the utilisation on every reported latency result, and publish percentiles rather than means by default. Say whether that was adopted or quietly ignored.
Follow-ups
- At what utilisation were both numbers taken, and why does that matter more than the change you made?
- Your open-loop generator cannot keep up at the target rate. How do you tell that apart from the system being slow?
- What would you need to see before trusting a latency claim from someone else?
Ship an order gateway with named, scheduled reconciliation debt
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.
How a strong candidate works it
- 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.
- 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.
- 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.
- 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.
- 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-ups
- 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?
Where candidates lose points
Treating a reported multi-part coding question as done once part one passes, then having to rewrite it when the extension arrives.
Several reported coding questions grow in stages. Validate brackets, then return exact positions for inline edits. Encode run-length data, then decode partial streams. Aggregate trade volume, then handle short sales and credit adjustments. If part one returns only a boolean, assumes the whole string is in memory, or stores unsigned quantities, part two becomes a rewrite. Before coding, ask what the likely extension is. Keep indices, carry decoder state across chunks, and use signed quantities, and keep part one's tests passing while you extend.
Calculating the three-player dice probability before pinning down the tie rules.
The reports differ on tie handling: one version resets the game if all three players tie, another says ties trigger a complete re-roll. Ask how a two-way tie for the highest roll is handled before computing. If the rule always ends with exactly one winner, for example a re-roll on any tie at the top, condition on the game not resetting instead of summing an infinite series by hand, check that the three win probabilities sum to one, and use symmetry as a check: with no advantaged player, each wins with probability 1/3. If a two-way tie means both win or nobody wins, the three probabilities neither equal 1/3 each nor sum to one, so say which rule you are using. For the variant where one player always rolls at least a 4, enumerate that player's three possible rolls explicitly.
Starting to code the bottom-insertion game board or the editor buffer before agreeing on the interface and representation.
In the reported board question, pieces enter from the bottom and push existing pieces up, so in a plain row-major grid every insert shifts a whole column. Write the public methods and one example state first. Pick a representation that makes the frequent operation cheap, such as a list per column, and state its cost. When checking for matches, point out that a bottom insert moves every piece in that column. Re-check that column and each row it intersects rather than only the new cell or the whole board.
Changing code or config in a debugging problem before a failing case has been reproduced and explained.
Reports list deep-dive debugging among the onsite technical rounds. Practise on tasks such as isolating a memory leak or a network timeout from failing service logs in a terminal. Say what the logs show, state one hypothesis, and name the command or log line that would confirm or rule it out. Only then change something. Use this guide's cache-stampede drill as practice: separate a rise in demand from a retry storm before proposing any fix.
Switching to OCaml, or another language you know only passively, to signal interest in the firm.
Reports say candidates may use any mainstream language and are not expected to know OCaml. An unfamiliar language costs you time on syntax and standard-library lookups in live coding, which is time taken away from correctness and testing. Interview in your most fluent language. Drill its calls for maps, sorting with a custom comparator, heaps and string splitting until you no longer need to look them up.
Design first, for a senior candidate
For a candidate senior enough that the loop turns on design and judgement rather than on whether the coding round gets finished. Five days build one system properly and then stress it; coding gets a single maintenance day, on the assumption that the risk at this level is an unexamined tradeoff rather than a missed algorithm.
Day 1 — Online assessment: test harness and spreadsheet
- Solve the reported bracket-validation and run-length encoding questions in a plain editor under a timer. Write a small test main first that feeds the stated examples plus an empty input and a single element.
- Some tracks report an Excel data analysis task. Practise the bank question on computing haircuts with Excel formulas, then rehearse lookups, conditional sums, absolute versus relative references and a pivot table on a raw dataset.
- After each problem, list the inputs your harness did not cover, add them, and rerun.
Deliverable: Two solved problems with their test harnesses, and one spreadsheet that cleans and aggregates a raw dataset with formulas you can explain line by line.
Day 2 — Coding questions that grow in stages
- Parentheses: write the validator with a stack, then extend it to return the matching index for each bracket and the position of the first unmatched one, keeping the part-one tests passing.
- Run-length encoding: write the encoder and decoder, then a stateful stream decoder that accepts chunks split mid-run and mid-count. Test a chunk boundary that falls inside a multi-digit count.
- Do the bank's Collapsible Code Editor: Brace Matching and Toggle, and say aloud which data structure part one should leave behind for part two.
Deliverable: Three solutions, each with part-one and part-two tests in one file, and a one-line note per problem on what you would have built differently had you known the extension.
Day 3 — Trade data, ordering and exact results
- Reported trade processing: aggregate volume per symbol and return the top trades with a size-k heap (O(n log k)), then extend it to short sales and credit adjustments using signed quantities.
- Work the bank's Top Trades From Event Sequence and Sort Trade Executions Into a Canonical Order, writing the comparator and its tie-break rule explicitly.
- Do the worked coding exercise on resolving positions across a corporate action graph (drill-coding-3). Compute the {C: 400, D: 250} fixture by hand before reading the solution.
Deliverable: Trade aggregation with short-sale handling and tests, a canonical-order comparator with its tie-break written out, and your hand computation of the corporate-action fixture.
Day 4 — Class design for stateful tools
- Reported game board with bottom insertion: write insert(column, piece) and winner() signatures first, choose a per-column representation, and state which rows a bottom insert changes. Then do the bank's Bottom-Insertion Connect Game: Detect the First k-in-a-Row Winner.
- Reported text editor buffer: support insertion, line-by-line navigation and selection expand/shrink. Compare a list of lines with a gap buffer for these operations and state the cost of each in your choice.
- Reported visual editor state tracking: build parenthesis matching, then store per-pair metadata such as depth and matching index so a renderer can highlight or fold blocks (see the bank's Visual Code Editor Folding).
- Reported class interface wrap: given a provided interface, write a wrapper that meets a target API, with explicit data mapping and error checks.
Deliverable: Four class designs, each with its public interface, its chosen representation, the cost of every operation, and one test per method.
Day 5 — Probability and combinatorics out loud
- Reported three-player dice game: ask how two-way ties at the top are handled. Under a rule that always ends with one winner (such as re-rolling any top tie), condition on the game not resetting and check that the results sum to one; then redo it with one player rolling at least a 4.
- Reported palindromic permutations: a palindrome exists only if at most one character count is odd, and then the count is floor(n/2)! divided by the product of floor(c_i/2)! over the characters. Verify on a small string by listing the permutations.
- Work the bank's Conditional Probability With Balls, Optimal Stopping With Square-Number Ruin and Optimize a Two-Box Place-or-Take Game, stating the sample space before any arithmetic.
Deliverable: Five worked probability or counting answers, each with a stated sample space and one sanity check (symmetry, sums to one, or brute-force enumeration) whose conditions you have confirmed hold.
Day 6 — System design, SQL and debugging
- Do the worked design exercise on recovering an order session that dropped mid-fill (drill-design-4) and check that your answer never resends the ambiguous submit.
- Do the worked SQL exercise on join fan-out (drill-sql-2): reproduce the inflated 3000 on the fixture, then 200 after the rewrite. Then attempt the position-limit race drill (drill-sql-1).
- Work the cache-stampede debugging drill (drill-debugging-5) and write the ordered checks before writing any fix.
- Sketch one bank design question, such as Process One Million Quote Updates per Second or Design Jane Street Orderbook APIs, starting from requirements, throughput and failure cases before any component.
Deliverable: Checked answers to the design and SQL worked exercises, an ordered debugging checklist, and one design sketch with its requirements and trade-offs written down.
Day 7 — Onsite rehearsal
- Run three to five mocks back to back to match the reported onsite day: one coding, one class design, one probability, and one design or debugging. Ask each partner to add a requirement midway.
- Prepare two short answers from the bank: why Jane Street, tied to the engineering work this role describes, and a walkthrough of one project with your part, one trade-off and what you would change.
- Go back over the misses from days 1-6 and redo the two problems you got most wrong, without notes.
Deliverable: Mock notes recording where your opening habits slipped in the later sessions, two rehearsed spoken answers, and the two redone problems passing their tests.