Interview guides · Aqr Capital Management
Aqr Capital Management Software Engineer Interview Questions & Guide 2026
Aqr Capital Management software engineer prep on operations: decide what you would need to see to know a release is going wrong.
The 60-second brief
As a Software Engineer at Aqr Capital Management, you play a vital role in building and maintaining the high-performance technology infrastructure that powers a quantitative investment giant. You contribute directly to portfolio analytics, trading systems, data pipelines, and core risk management applications. Your daily work involves designing scalable solutions that handle massive volumes of financial and market data under strict latency and reliability constraints.
Browser-facing seats are not covered by algorithm practice. Scope in state ownership, what the page does on a slow or failed request, and how you would diagnose something that renders correctly but feels slow.
Focus areas
- Replay recorded input through the same build
- Treat a send timeout as unknown, not failed
- Fold execution reports idempotently on venue exec id
Overview
The scope of this role spans across modern backend development, data engineering, and system design, heavily leveraging languages like Python, Java, C++, and.NET Core. Whether you are optimizing distributed algorithms, constructing robust SQL and NoSQL database schemas, or streamlining portfolio analytics platforms, your contributions directly impact how quantitative researchers and portfolio managers deploy strategies. The complexity of the financial domain means you will constantly balance architectural elegance with pragmatic engineering trade-offs.
Expect an environment that demands rigorous technical proficiency, intellectual curiosity, and an appreciation for clean code. You will collaborate closely with quantitative researchers, data analysts, and fellow engineers to translate complex mathematical models into production-grade software. While the interview loop and internal processes are notoriously demanding, the problems you solve offer immense intellectual stimulation and sit at the very heart of modern quantitative asset management.
The Aqr Capital Management interview process
Aqr Capital Management candidates report 4 rounds · ≈ 3-5 weeks. The stages below are what candidates describe, not a published process.
Online Assessment
Most of the time lost in this format is not lost to thinking. It goes to a standard-library call you half-remember, an off-by-one in a loop bound, and a debugging loop that mutates code at random until something passes. When output is wrong, stop re-reading the whole function: take the smallest input that reproduces it and walk the state through by hand, printing intermediates if the environment allows. Guessing at a fix without a failing case you understand is how a five-minute bug becomes twenty, and the clock does not pause while you do it.
What to demonstrate
- Whether you reach the right structure without a detour, and can write it from memory rather than only recall that one exists
- Whether overflow is considered where the language has fixed-width integers, since a signed 32-bit value stops at 2,147,483,647 and then wraps in Java, is undefined behaviour in C++, and does not arise in Python, whose integers grow instead
- Whether recursion depth is treated as a constraint on large inputs, given that CPython's default limit is 1000 frames and a deep recursion can exhaust the stack in any language where an iterative version would not
- Whether a failing case is isolated and explained before any edit is made to the code
How to prepare
- From an empty file and with no references open, implement the pieces you lean on most: a heap push and pop, an iterative DFS with an explicit stack, and a binary search whose midpoint is written lo + (hi - lo) / 2, which avoids the overflow that (lo + hi) / 2 can hit in a fixed-width integer type
- Time yourself on the ten library calls you look up most, such as sorting with a custom comparator, splitting and joining strings, and finding the next key at or above a value in an ordered map, until the lookup is gone
- Take a solution you know is broken and, before touching it, write one sentence naming the input, the expected value and the actual value. Repeat until you do it without deciding to.
Recruiter Phone Screen
Half of this call is the part candidates treat as small talk: start date, notice period, work authorisation and its timing, location and time zone, on-call, and the number. Those are what kill offers late, after several engineers have each spent a day. Surfacing a hard constraint now costs you nothing and occasionally buys you something, since a loop compressed to fit a competing deadline can usually only be arranged if it is asked for early. The common failure is deflecting the compensation question twice, then discovering at offer stage that the band never reached your number.
What to demonstrate
- Whether your hard constraints are compatible with the role before a loop gets booked: earliest start, notice period, what authorisation you hold and when it needs action, days on site, willingness to carry a pager
- Whether you give a compensation range with something behind it, such as current total compensation or a competing timeline, rather than leaving the band untested
- Whether your stated timeline is real, since a competing deadline raised now is something scheduling can sometimes work around and the same deadline raised at offer stage usually is not
How to prepare
- Write each constraint down in one line before the call and state them as facts rather than negotiating them live under a question you were not expecting
- Set your range from two or three current data points for that level and location, and name the structure you are quoting in, so the number is comparable to the one they are holding
- If another process is running, say where it stands and by when, and ask directly whether this loop can be scheduled inside that window
Technical Phone Screens
The person on this call usually cannot evaluate your code and does not need to. They write a short paragraph, and that paragraph is what a hiring manager skims when deciding who to put on your loop. So the test is not whether your work was hard, it is whether a non-engineer can repeat it correctly. Name systems by what they did rather than by their internal codename, give each project a shape (what was breaking, what you changed, what happened after), and keep the whole walkthrough near ninety seconds. Depth that cannot survive a paraphrase reads as vagueness.
What to demonstrate
- Whether a non-engineer can restate your projects without distorting them, since their paraphrase is what travels to the hiring manager, not your sentences
- Whether each project has a shape rather than a stack list: the failure or constraint, the change you made, the result and how it was measured
- Whether you can say what was yours inside a team project without either inflating it or disappearing into the plural
How to prepare
- Rewrite each headline project as two sentences with no internal system names and no acronyms outside your company, then say them to someone outside engineering and have them repeat them back. Fix whatever came back wrong
- Attach one measured number to each project: the baseline, the change, and the window it was measured over. Where nothing was ever measured, say that plainly rather than reaching for a plausible percentage
- Time the background walkthrough against a clock. If it runs past two minutes, compress the earliest role to a single clause and spend the recovered time on the most recent one
Onsite Interview
Nobody in the room with you decides this. Interviewers typically write their rounds up separately, often before seeing anyone else's, and the outcome is settled later from those write-ups. A split panel gets resolved by whichever note carries specific evidence, so what you want out of each room is one concrete thing that person could write down: a bug you caught yourself, a trade-off you named, a decision you owned. The rest is arithmetic. The project you describe in a behavioural conversation is often the same system you sketched an hour earlier, and the two accounts have to agree.
What to demonstrate
- Whether the scale, team size and timeline you attach to a project hold steady when that project resurfaces in a different round
- Whether each interviewer leaves with a specific thing to cite rather than a general impression of competence
- Whether a trade-off you defended in one round survives a challenge in another, instead of being quietly swapped for the answer the new interviewer seemed to want
- Whether a question you have already answered earlier in the day gets the same answer at the same depth, without visible impatience
How to prepare
- Write a one-page sheet per project fixing the figures you will quote — request volume, data size, team size, elapsed time, what broke — and say them aloud from the sheet until they come out identical every time
- For each round on the schedule, decide in advance the one sentence you want in that person's notes, then check in a mock that you said it outright instead of leaving it to be inferred
- Have someone ask you the same project question twice, an hour apart, and diff the two answers for numbers that moved or a trade-off that reversed
Where candidates lose points
Reading the wall clock inside strategy or matching logic instead of taking time from the event stream.
It breaks replay outright - the same inputs stop producing the same outputs - and it adds a subtler failure. CLOCK_REALTIME is adjustable, so an NTP correction can step it backward and produce a negative interval or a timer that fires twice. CLOCK_MONOTONIC never steps backward but is not comparable across machines and, on Linux, does not advance while the machine is suspended, so it is the right clock for measuring a duration and the wrong one for stamping an event. Event time has to flow through the system as data, with the wall clock consulted only at the ingestion boundary where a receipt timestamp is taken once and then carried.
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.
Listing technologies instead of trade-offs
Name the property the design needs first, such as ordered range scans, multi-entity transactions, cheap appends, or a predictable p99, then pick something that provides it and say what it gives up in exchange. Almost any component is defensible once you state the requirement it satisfies and the one it sacrifices.
Going silent while thinking
Narrate the candidates and why you are discarding them, even in fragments: sorting first would make this a two-pointer scan, but it destroys the original indices, which the output needs. From the other side of the table, a candidate thinking hard and a candidate stuck are indistinguishable until one of them speaks.
Questions & practice
Implement x ^ y and power(x, n).
Medium · data structures and algorithms
Approach
- Walk one small example through your approach before writing the whole thing.
- Name the brute-force solution and its complexity before improving on it.
- State the target complexity and say which constraint rules the naive version out.
Follow-up
- How does this change if the input no longer fits in memory?
- Which test case would catch an off-by-one here?
Write regex for verifying a simple URL.
Medium · data structures and algorithms
Approach
- Choose the data structure from the access pattern, not from familiarity.
- State the target complexity and say which constraint rules the naive version out.
- Name the brute-force solution and its complexity before improving on it.
Follow-up
- Which test case would catch an off-by-one here?
- How does this change if the input no longer fits in memory?
Find the substring with the largest sum.
Medium · data structures and algorithms
Approach
- Restate the input: its shape, its size, and what is guaranteed about it.
- Walk one small example through your approach before writing the whole thing.
- Choose the data structure from the access pattern, not from familiarity.
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?
Find a missing number with a memory constraint.
Medium · data structures and algorithms
Approach
- State the target complexity and say which constraint rules the naive version out.
- Restate the input: its shape, its size, and what is guaranteed about it.
- Walk one small example through your approach before writing the whole thing.
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?
Implement a decorator in Python.
Medium · languages, concurrency and fundamentals
Approach
- Identify the window where an invariant is briefly untrue.
- Distinguish a value from a reference to it, and say which one you handed out.
- Reach for the cheapest primitive that closes the race, not the broadest lock.
Follow-up
- What happens if two callers reach this at the same time?
- How would you prove the race exists rather than suspect it?
Which language is faster between C++ and Python and why?
Medium · languages, concurrency and fundamentals
Approach
- Say what the runtime actually does before reasoning about the code.
- Reach for the cheapest primitive that closes the race, not the broadest lock.
- Identify the window where an invariant is briefly untrue.
Follow-up
- Where could this allocate more than you expect?
- What happens if two callers reach this at the same time?
Cap outbound message rate over a rolling one-second window
Easy · sliding window · ring buffer · rate limiting
Outbound order messages carry int64 nanosecond send timestamps, non-decreasing, up to 10^7 per session. A venue caps you at M admitted messages in any rolling one-second window, and exceeding it risks a session-level throttle or a disconnect. Implement admit(t) on the order path: it must not allocate, must not read a clock of its own, and must run in constant time. Return the number of denials and the timestamp of the first one. State your memory footprint at M = 100 and your window boundary convention.
Approach
- Observe that only admitted messages enter the window, because the cap is on what the venue received. That single decision collapses the general sliding-window-with-eviction problem into a fixed-size one: at most M timestamps can ever be in the window, so the structure is a ring buffer of M int64s plus a monotone head counter, never a deque that grows.
- admit(t) compares t against the timestamp M positions back: if head < M, admit; otherwise admit iff t - ring[head mod M] is at least 1e9. On admit, write t at head mod M and increment head. That is two loads, one compare, one store, no branch on data size, O(1) time and 8M bytes of state — 800 bytes at M = 100, about thirteen cache lines, allocated once at startup.
- Fix the boundary convention and write it down: treat the window as (t - 1e9, t], so a message exactly 1,000,000,000 ns old has left it. The opposite convention is equally defensible but must be the same one the venue uses, and the off-by-one only ever shows up as an unexplained session throttle under burst.
- Take t from the event rather than from a clock call. A CLOCK_REALTIME read on this path is both a syscall risk and a determinism break: a replay of the same recording must make the same admit decisions, which it cannot do if the limiter samples wall time.
- Note the property this does not give you: strict rolling-window counting is bursty by construction — M messages can land in the first microsecond of the window and then nothing for a second. A token bucket smooths that but no longer implements the venue's stated rule, so it is a different contract, not an optimisation.
Worked solution (15 min)
- Set M = 3 and work in milliseconds for legibility, with the window as (t - 1000, t].
- Feed the sequence 0, 10, 20, 500, 1005, 1010 through admit(t) by hand, writing down the ring contents and head after each call.
- At t = 500, compare against ring[(3-3) mod 3] = 0: the gap is 500 ms, so deny and leave the ring untouched.
- At t = 1005 compare against ring[0] = 0 (gap 1005, admit, overwrite ring[0]); at t = 1010 compare against ring[1] = 10 (gap exactly 1000, admit under the half-open convention).
- Re-run with the window defined as [t - 1000, t] instead and note which decision flips.
Expected result: Five admitted, one denial, first denial at t = 500 ms. Final ring holds {1005, 1010, 20} with head = 5. Under the closed-boundary convention, t = 1010 is denied instead, giving four admitted and two denials.
Verify it yourself
- For every admitted message, a brute-force count of admitted timestamps in (t - 1000, t] is at most 3 — run that O(n^2) check against the ring version on a random 10,000-message fixture.
- An allocation counter reads zero across the whole run, and the structure's size does not depend on n.
- Replaying the identical timestamp sequence twice yields identical admit decisions, including the denial position.
Follow-up
- The venue publishes two caps, M per second and N per ten seconds. How does the structure change, and what is the memory cost?
- You must also cap by scope — per session, per account, per instrument. Where does that stop being O(1)?
- A denied message still has to go somewhere. What do you do with it, and what does the strategy see?
A desk report that silently multiplies filled quantity
Medium · join-fanout · aggregation · query-rewrite
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.
Approach
- 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.
Worked solution (30 min)
- Reproduce on a fixture: one order with five lifecycle rows and two fills of 100 each, and one limit scope carrying three versions.
- Run the naive join and confirm filled quantity comes back as 200 * 5 * 3 = 3000.
- Rewrite with a pre-aggregated execution subquery and a LATERAL limit lookup, and confirm 200.
- Add a second order to the same run and confirm the run total is the sum of the two orders, not a multiple of it.
- Add a sixth lifecycle row to the first order and confirm no total moves.
Expected result: 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.
Verify it 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-up
- 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?
Denormalising instrument attributes onto the position snapshot
Medium · denormalization · read-patterns · temporal-joins
A risk dashboard reads every open position for a desk once a second, roughly 50,000 rows, and each row needs price_scale, contract_multiplier and currency from instrument_version resolved as of now, plus a mark price. Today that is a range-predicate join per row against an effective-dated table. Propose a model that serves this read, state exactly which columns you copy and what makes the copy safe, and say what happens to yesterday's snapshot rows when a vendor correction changes an instrument version that was already in effect.
Approach
- Characterise the read before choosing a shape: 50,000 rows a second, attributes immutable within a version, versions changing at most daily for almost every instrument. Be accurate about why the as-of join costs what it does, because the tempting explanation is wrong. The join carries instrument_id equality alongside the effective-dating predicate, so the planner hashes or merges on that key and applies effective_from <= now() AND (effective_to IS NULL OR effective_to > now()) as a filter qual; a range predicate does not prevent a hash join. The real costs are that the scanned side is the whole version history rather than the single row per instrument actually in effect, that two correlated inequalities are a selectivity the planner estimates badly, and that the answer is recomputed every second while being constant for the entire business date. On 50,000 positions against 150,000 stored versions the hash join discards two rows for every one it keeps, and the per-row nested loop a bad estimate would choose instead costs 50,000 index descents and roughly three times the runtime.
- Prefer the middle option to either extreme. Materialize an as-of dimension once per business date, one row per (instrument_id, business_date) resolved at session start, and make the dashboard an equality join on that pair. The range predicate disappears without duplicating attributes onto every position row.
- If you do copy onto position_snapshot, copy price_scale, contract_multiplier and currency together with their provenance: the effective_from (or a version surrogate) of the instrument_version they came from. A denormalised copy without provenance cannot be audited, cannot be reconciled and cannot be replayed.
- Handle the intraday hazard explicitly instead of hoping: a corporate action effective mid-session changes contract_multiplier within a business date, so a per-date dimension is wrong for that instrument that day. Either key the dimension at a finer grain for affected instruments or fall back to the as-of join for them, and know which instruments those are from the corporate-action feed rather than from a support ticket.
- Answer the correction question the books-and-records way: a derived snapshot records what the system believed when it computed, so a later vendor correction does not rewrite yesterday's rows. It produces a new snapshot row or a break, which is the same principle that keeps eod_derived and eod_reconciled as separate rows rather than one mutable number.
- State the cost you accepted: writes amplify, the dimension build becomes a session-start dependency, and a missed rebuild serves yesterday's attributes silently, so the dimension needs a freshness assertion that fails the dashboard rather than a scheduled job and optimism.
Follow-up
- The dashboard now wants 30 days of the same view. Does the per-date dimension still work, and what does the join key become?
- Where does mark_px live in your model, and what stops a stale mark from being indistinguishable from a fresh one?
- How large is the dimension, and does it fit in memory on the service that serves the dashboard?
Walk through the architecture of a complex application from your past experience.
Medium · system design and architecture
Approach
- Choose a partition key and say what query it makes expensive.
- Fix the scope first: who calls this, how often, and what they do when it fails.
- State the consistency you need, and where you are willing to be stale.
Follow-up
- How does this behave when that dependency is down for an hour?
- What breaks first when traffic grows ten times?
Discuss the drawbacks of different design patterns.
Medium · system design and architecture
Approach
- Name the failure you are designing for, then the recovery path.
- State the consistency you need, and where you are willing to be stale.
- Choose a partition key and say what query it makes expensive.
Follow-up
- How does this behave when that dependency is down for an hour?
- What breaks first when traffic grows ten times?
Explain ACID and BASE properties along with inheritance and abstraction in detail with examples.
Medium
Approach
- Say what you would check first and why it is the highest-information step.
- Clarify what is being asked and what a complete answer contains.
- State your assumptions explicitly before working the problem.
Follow-up
- What assumption would you test first?
- How would you know your answer was wrong?
Distribute risk limits to gateways with no bypass path
Medium · consistency vs availability · fan-out · versioned snapshots · fail-closed
risk_limit is versioned on (limit_id, limit_version) with scope_type and scope_id, limit_type, limit_value, enforcement ('block', 'warn' or 'monitor'), effective_from, effective_to and an approver. Forty order-gateway processes must evaluate a few hundred applicable rows per outbound order inside a single-digit microsecond budget, with no code path that skips the check. Limits change during the session, and a change must not apply retroactively to an order already approved. Design distribution and evaluation: the in-process structure, how a new version reaches forty processes, what a process does when the distributor is unreachable, and what each check records.
Approach
- Do scope resolution at snapshot build time, not per order. Precompute the applicable limit set for each (account, strategy, instrument) into a flat structure so the per-order check is a handful of array reads and integer comparisons. Resolving firm, desk, account, strategy, instrument and asset-class scopes on every order is what pushes this out of budget.
- Publish immutable snapshots with a monotonic version and swap a pointer. No reader observes a half-applied change, and an order approved against version N stays approved because the rule is 'the version in force at the instant of the check'. That is exactly what has to be recorded, so write the snapshot version onto the order_event row.
- Treat the fan-out as forty independent failures rather than one publish. A snapshot landing on 39 of 40 gateways means two orders from one account face different limits in the same second. Acknowledge per process and expose the minimum applied version across the fleet as a health metric, because published is not in force.
- Decide the unreachable case explicitly and go to consistency. Serve the last snapshot for a configured maximum age, then refuse new orders, or admit only risk-reducing ones, with a specific reject reason code. Serving a limit of unknown age is a compliance failure as well as a bug in most regulated markets, where the control is required to be non-bypassable.
- Keep the no-bypass property in the code, not in a review convention: one function on the send path, no flag, no fast path, and a test that fails if a second route to the transport exists. The pressure to add one is permanent and arrives with a legitimate case, so make the check cheap enough that the argument does not start, and measure it at p99.9 from a histogram rather than as a mean.
Worked solution (25 min)
- Build the snapshot structure for 500 limit rows across all six scope types and 10,000 accounts, then time the per-order check over 1,000,000 orders with a high-resolution timer, publishing p50, p99 and p99.9 from a high-dynamic-range histogram.
- Publish a new snapshot while the check loop runs and record the version stamped on every order across the swap.
- Stop the distributor and let the held snapshot age past the configured bound.
- Enumerate every code path from an order to the venue transport and count how many pass through the check function.
Expected result: The per-order check is constant time in the low microseconds or below, with p99.9 within a small multiple of p50 and no allocation in the path. Across the swap every order carries exactly one snapshot version, orders before it carrying N and after it N+1, with no order carrying a mixture of values from both. Past the staleness bound the gateway rejects new orders with a specific reason code instead of continuing on the held snapshot. The path count is one.
Verify it yourself
- Every order_event row carries the limit snapshot version its check evaluated against.
- A rejection can be explained months later from risk_limit alone: which limit_id, which limit_version, which value, approved by whom.
- The minimum applied snapshot version across the fleet is a monitored number rather than something looked up during an incident.
Follow-up
- A limit tightens at 10:00:00.000 while an order approved at 09:59:59.999 is still in flight. What happens to it, and who is entitled to decide?
- Enforcement moves from 'monitor' to 'block' on a limit that has never fired. What do you look at before flipping it?
- One gateway is stuck three versions behind. How do you learn that before an order does?
Replay diverges once in twenty runs on wide machines
Hard · determinism · concurrency · floating point
Replaying one recorded session through one build produces outbound order streams that differ in about one run in twenty. It reproduces only on 32-core hosts, never under the thread sanitizer, and never in the single-threaded debug build. Two divergence shapes appear: two orders swapping position in the stream, and one limit price differing in the last digit. Give an ordered checklist that locates the first diverging decision and separates the two causes, naming what you would pin and in what order.
Approach
- Make the divergence cheap to locate. Byte-diffing two output streams finds the first differing byte, which is usually far downstream of the first differing decision. Hash each decision into a running chain keyed by the input event's sequence number; the first differing chain entry names the event and the decision, turning a rerun-until-it-happens loop into a single comparison.
- Separate the two shapes, because their cause sets are disjoint. An ordering difference is an interleaving or an iteration-order problem. A value difference in the last digit is arithmetic, and the precise statement matters: one binary executing the same operations in the same order on the same inputs is bit-reproducible, so a value differing across runs means the order of operations changed or a different code path executed.
- For the ordering shape, enumerate and pin one at a time: more than one thread consuming the input stream; a work-stealing pool whose chunk boundaries vary with scheduling; a container whose iteration order is not a function of insertion order - a map keyed on a pointer whose value moves with address layout, a runtime with randomised map iteration or a per-process hash seed, or a set of objects with no stable hash. Force single-consumer ingestion inside the strategy boundary and replace those containers with ordered ones keyed on instrument_id or sequence number.
- For the value shape, pin the arithmetic. A parallel reduction whose partition depends on thread count or on stealing gives a different summation order and therefore different rounding; runtime dispatch to a different vector kernel does the same. Then pin the compiler: fast-math permits reassociation, and contracting a multiply and an add into a single fused operation changes the rounding of the intermediate. Build with contraction disabled and fast-math removed, and make any reduction's partition a function of input length alone.
- Do not treat a quiet sanitizer as a result. It perturbs timing and allocation enough to close the window and instruments only what it can see, so its silence is weak evidence. Run it, but prove the fix by making the run deterministic by construction and then replaying the recording several hundred times with a zero-tolerance comparison.
- Keep it from returning. Add a CI job that replays a fixed recording N times and fails on any difference, and enforce that nothing inside the strategy boundary reads a wall clock: event time arrives as data, and the wall clock is consulted once at ingestion to stamp received_ts.
Follow-up
- It will not reproduce on an 8-core host. Why would core count matter, and does that narrow which of the two shapes you are chasing?
- A colleague proposes comparing prices with a 1e-9 tolerance so the test goes green. What does that cost you six months from now?
- The strategy needs a 200 ms timeout. How do you express it without reading a wall clock?
Suggested practice checklist
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 — Numbers before diagrams
- Build your own reference card of the figures you will re-derive all week: bytes for a realistic record, requests per second implied by a given daily active count, and the storage that a year at a given write rate produces. Derive each one rather than copying it, because the derivation is what survives a follow-up.
- Turn one product statement into capacity requirements. From ten million daily users at four writes and forty reads each, state the peak-to-average factor you are assuming and why, then produce peak write QPS, peak read QPS and a year of storage.
- Write the two numbers whose order of magnitude changes the design, the read-to-write ratio and the working-set size against memory per node, and state the threshold at which each one flips your answer.
Deliverable: A one-page numbers card and one worked capacity estimate with every assumption written down.
Day 2 — One system, from requirements to schema
- Spend the first ten minutes producing only functional requirements, non-functional targets with numbers attached, a p99 latency, a durability expectation, a consistency requirement, and an explicit out-of-scope list.
- Define the interface before the boxes: the three or four endpoints, their parameters, what each returns, and which of them are idempotent.
- Write the data model, then write the single access pattern that justifies it, and state what the schema would have to become if the dominant access pattern were the other one.
Deliverable: One design carried to endpoint-and-schema depth, with non-functional targets expressed as numbers and a written out-of-scope list.
Day 3 — The consistency you are actually buying
- Write out what a client sees under asynchronous replication when its write commits on the leader and its next read is served by a lagging follower, then write the two fixes, pinning that session's reads to the leader for a bounded window or carrying a version token the replica must reach, and the cost of each.
- Work the quorum arithmetic on paper for N of three with W and R of two, and separate what R + W > N does guarantee, that any read set intersects any write set, from what it does not: on its own it is not linearizability, and a sloppy quorum that accepts writes on nodes outside the preference list breaks even the intersection.
- Take two storage choices with different defaults, a single-leader relational store committing synchronously and a quorum-replicated store that converges eventually, and write the specific product behaviour that would be wrong under each, rather than a general statement about which is stronger.
Deliverable: A page separating what quorum overlap guarantees from what it does not, with one concrete product misbehaviour attached to each gap.
Day 4 — Failure is the design
- For one write path, work through the case where the client times out after the server has already committed, then design the idempotency key: who generates it, how long it is retained, and what the duplicate request returns.
- Express the retry policy as parameters rather than as a word: maximum attempts, base delay, backoff factor, jitter, and which error classes are retried at all. Then state why retrying a non-idempotent write without a key is a correctness bug and not merely waste.
- Compute the fan-out effect on tail latency. If a request waits on ten backends and each independently exceeds its p99 one percent of the time, the chance at least one is slow is 1 - 0.99^10, about ten percent. Then write why independence is the optimistic assumption and what correlates them in practice.
- Name the backpressure mechanism for one queue or one dependency in the design, a bounded queue with shedding or a concurrency limit, and write what the caller is told when it engages.
Deliverable: One write path with an idempotency design, a parameterised retry policy, and a written tail-latency calculation with its assumption named.
Day 5 — Scaling the hot path
- Choose cache-aside or write-through for one read path and write the staleness window each produces, then name the invalidation event and what the system does when that event is lost.
- Design against the stampede: either coalesce requests so only one recomputes a missing key, or refresh early with jittered expiry, and write why identical TTLs on keys populated in the same moment produce a synchronised expiry and a thundering herd.
- Shard one table by a key you choose, then answer the two questions that break the choice: which queries now require a scatter-gather, and what happens to the distribution when one tenant is a hundred times larger than the median.
- Write the cost of adding a node under plain modulo placement, where nearly every key moves, against consistent hashing, where roughly one key in n+1 moves, and state what virtual nodes are for.
Deliverable: A caching and sharding decision for one path, each with its failure mode and its rebalancing cost written beside it.
Day 6 — Keep the coding hand in, at the bar that applies to you
- Solve one medium problem in thirty minutes, then spend twenty more making it production-shaped: named invariants, validation at the boundary, and errors that distinguish a caller mistake from an internal fault.
- Write the tests you would require of a colleague's version of that function: one for empty input, one for the boundary, and one for the case the implementation is most likely to get wrong.
- Read a piece of your own code from six months ago and write the change you would ask for, phrased as you would actually phrase it in review.
Deliverable: One problem hardened to review standard, with its test list and one written review comment.
Day 7 — Defend it while being interrupted
- Run a forty-five-minute design mock with an interviewer briefed to change a requirement halfway, a tenfold traffic increase or a new strict consistency requirement, and to push on one number you estimated.
- Rehearse the two sentences a senior loop is listening for: naming the tradeoff you are choosing against and why, and saying what you would measure to learn that the choice was wrong.
- Prepare the design you regret: a real decision, the constraint that produced it, what it cost, and what you changed afterwards.
Deliverable: Mock notes recording how the design changed under the new requirement, plus a written account of one regretted decision.
Scope is read from what you decided
Team size, service count and tickets closed say very little. Seniority shows in the decision you owned: what you chose not to build, which constraint you traded away, whose objection you had to resolve before anything could move. A large project where you executed someone else's plan is a small story.
Tell me about yourself and why you applied for this position at Aqr Capital Management.
Medium · behavioural and engineering judgement
Approach
- Name the disagreement and how you resolved it with evidence.
- Close with what you would do differently, concretely.
- State the situation in two sentences and spend the rest on the reasoning.
Follow-up
- What did you decide not to do, and why?
- What would you do differently if you ran that again?
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
- 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-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?
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
- 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.
- 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.
- 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.
- 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.
- 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.
- 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?