A Software Engineer at McKinsey Quantumblack sits at the intersection of advanced analytics, data science, and high-impact engineering. You are not just writing code; you are building the robust, scalable technical foundations that allow McKinsey to deliver transformative insights to the world’s most influential organizations. Your work directly impacts how complex data models are deployed, integrated, and scaled to solve real-world business challenges.
This role is inherently collaborative and intellectually demanding. You will work alongside data scientists and consultants to turn sophisticated prototypes into production-grade software products. Because the problems you tackle are often novel and ambiguous, you must be comfortable with high-level architectural thinking and meticulous implementation. Being a Software Engineer here requires a blend of technical rigor, a deep understanding of software craftsmanship, and a commitment to delivering excellence in high-stakes environments.
Initial Screening
reportedHalf of this call is the part candidates treat as small talk: start date, notice period, work authorisation and its timing, location and time zone, on-call, and the number. Those are what kill offers late, after several engineers have each spent a day. Surfacing a hard constraint now costs you nothing and occasionally buys you something, since a loop compressed to fit a competing deadline can usually only be arranged if it is asked for early. The common failure is deflecting the compensation question twice, then discovering at offer stage that the band never reached your number.
What to demonstrate
- Whether your hard constraints are compatible with the role before a loop gets booked: earliest start, notice period, what authorisation you hold and when it needs action, days on site, willingness to carry a pager
- Whether you give a compensation range with something behind it, such as current total compensation or a competing timeline, rather than leaving the band untested
- Whether your stated timeline is real, since a competing deadline raised now is something scheduling can sometimes work around and the same deadline raised at offer stage usually is not
How to prepare
- Write each constraint down in one line before the call and state them as facts rather than negotiating them live under a question you were not expecting
- Set your range from two or three current data points for that level and location, and name the structure you are quoting in, so the number is comparable to the one they are holding
- If another process is running, say where it stands and by when, and ask directly whether this loop can be scheduled inside that window
Technical Assessment
reportedWhat this round decides is narrow: whether you can produce code that runs and is correct on inputs nobody showed you. An elegant solution that does not compile scores below a plain one that does, so write a correct brute force first, say out loud that you know its cost, and improve it with the working version still on screen. What separates strong answers is who finds the broken case. Trace your own code against an empty input, a single element, and duplicate keys before you say you are finished, because being told is far more expensive than noticing.
What to demonstrate
- Whether degenerate inputs get checked without being asked for: an empty collection, one element, every element equal, and the extreme value the input type allows
- Whether the complexity you state matches the code you actually wrote, including a sort or a copy sitting inside a loop
- Whether the finished answer is verified against the worked examples before you call it done, rather than assumed correct because the code reads correctly
How to prepare
- Take five problems you have already solved and, without running anything, write down what each returns for empty input, a single element, and all-duplicates. Then run them and count how many you predicted wrong.
- Drill the brute force as its own skill: on ten problems, write only the obviously-correct slow version and time how long it takes to get it passing. If that is more than a few minutes, that is what to practise, not the optimal version.
- Add a fixed last step before you submit anything, reading only the loop bounds and the initial value of each accumulator, which is where most off-by-one errors live
Pair Programming
reportedBecause the format is not fixed, the first job in the room is classification. Listen to the opening question and decide what it is: a probe into work you have already described, a fresh problem to solve now, or a conversation about how you operate. Each wants a different register, and the common failure is forcing a rehearsed structure onto a question that did not ask for it. Running a full design ritual on a ten-minute debugging question reads as not listening. When you cannot tell which it is, ask how long they want to spend and answer at that depth.
What to demonstrate
- Whether the shape of your answer matches the question, so a yes-or-no gets answered before it is justified and an open prompt gets a direction before a detour
- Whether you check how much depth is wanted instead of deciding for them, and whether you stop when the answer is complete rather than continuing until someone interrupts
- Whether you can be redirected in the middle of an answer without restarting it from the beginning
- Whether a question outside your experience gets an honest boundary followed by reasoning from what you do know, instead of a confident answer with nothing behind it
How to prepare
- Rehearse one project at three lengths, roughly thirty seconds, three minutes, and a full walkthrough at the depth of a design review, and practise switching between them when someone interrupts mid-telling
- Have someone ask you five questions of deliberately mixed type in one sitting without telling you the types, and score only whether you identified each one correctly before you started answering
- Draft the sentence you will use to check depth, along the lines of asking whether the short version is useful here or they want the detail, and use it in a real conversation this week so the day of the round is not its first outing
PracHub editorial advice for the preparation topics above.
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.
Reporting mean or median latency, and benchmarking with a harness that stops issuing load while the system is stalled.
The distribution that matters is the tail - p99.9 is where a stall meets a burst, and it is precisely the sample an average hides. The common closed-loop harness sends a request, waits for the reply, then sends the next, so it never issues the requests that would have arrived during a stall and never samples the stall at the arrival rate it would have faced. That is coordinated omission, and it can understate the tail by an order of magnitude. Measure with an open-loop generator that holds the intended send schedule, record latency from intended send time rather than actual, and publish percentiles from a high-dynamic-range histogram rather than from a running average.
Tests that assert on the implementation rather than the behaviour
Assert on what a caller can observe, not on the number of internal calls or the shape of a private field. A test that breaks on every refactor but still passes when the answer is wrong costs more than it protects.
Comparing floating-point values for equality, or holding money in them
Binary floating point cannot represent 0.1 exactly, so repeated addition drifts and an equality check fails on values that are mathematically equal. Store currency as integer minor units or a decimal type, and compare floats against a tolerance you chose for a stated reason.
Choose a category, try a prompt, then open its approach, worked solution or follow-up when you need it.
Attribute every order to the limit version in force
You have 10^7 order_event rows of type submit sorted by sent_ts, and 10^5 risk_limit versions carrying (limit_id, limit_version, scope_type, scope_id, limit_type, limit_value, effective_from, effective_to nullable). For every order and every scope that applies to it — firm, desk, account, strategy, instrument, asset_class — produce the (limit_id, limit_version) that was in force at sent_ts. Windows are half-open. Write the offline attribution job, state its complexity against a per-order binary search, and define exactly what happens to an order sent at the microsecond a new version becomes effective, and to one whose scope has no version at all.
Approach
- Group the version table by (scope_type, scope_id, limit_type) and sort each group by effective_from. Assert non-overlap inside each group first; the whole job assumes at most one version is in force per group per instant, and an unchecked assumption there produces a plausible answer rather than an error.
- Partition the orders by the same key. Orders already arrive sorted by sent_ts, so each group is a sorted merge: advance the version pointer while the next version's effective_from is at or before the order's sent_ts, then emit the current one. O(n + v) after partitioning, against O(n log v) for a binary search per order.
- Be honest about why the merge wins. At v = 10^5, a binary search is about 17 probes into an array that fits in L2, so the log factor is not the problem; the merge wins because it is a single sequential pass over both inputs with no random access, which at 10^7 orders is a memory-bandwidth story rather than a complexity one.
- Pin the boundary in code and in a test: the window is [effective_from, effective_to), so a version effective at 10:00:00.000000 does not bind an order sent at 09:59:59.999999 and does bind one sent at 10:00:00.000000. The columns are microsecond-precision, so exact ties are common enough to hit in production, not a theoretical edge.
- Use sent_ts rather than decision_ts or the ack's received_ts, because the property being audited is that the order was evaluated against the limits in force when it went out. Record which timestamp you used in the output schema so a later reader cannot silently reinterpret it.
- Emit an explicit no-limit-in-force marker with a reason code when a group has no covering version, rather than omitting the row. An absent row reads as unlimited, and unlimited is the one answer a risk report must never produce by accident.
Follow-up
- Move this into the live pre-trade path. How does the check read a versioned snapshot without a lock, and how does it record the version it evaluated against?
- A limit tightens at 10:00:00 while three orders are in flight. What should happen to them, and which component decides?
- Scopes nest — a firm limit and an account limit both apply. Do you attribute all of them or only the binding one, and what does the answer cost you at 10^7 rows?
Choose a book structure for constant-time top-of-book reads
You maintain a price-level book for one instrument on one venue. Deltas arrive as (side, price, new_aggregate_qty), where price is an integer at the instrument's price_scale and tick_size is constant within the session; new_aggregate_qty of zero deletes the level. Sustained 10^5 to 10^6 deltas per second, over 90 percent landing within ten ticks of the touch, and top of book is read after every delta. Choose the representation, then give apply(delta) and best_bid()/best_ask() with their expected cost, their worst case, and the memory per instrument per side. Say what happens when the price leaves your band.
Approach
- Derive the structure from the access pattern rather than from the abstract operation set: reads are overwhelmingly of one element (the touch), writes cluster within ten ticks of it, and the price domain is a dense integer lattice because every valid price is a multiple of tick_size. That argues for a flat array of aggregate quantities indexed by (price - base_px) / tick_size, with cached best_bid_idx and best_ask_idx, over any comparison-based container.
- apply(delta) writes one slot in O(1). An insert at a price better than the current best updates the cached index in O(1) with no scan. A delete of a non-best level is O(1). Only a delete of the best level requires finding the next occupied level inward, which is where the worst case lives.
- Make that scan cheap by shadowing the array with a bitset, one bit per level, 64 levels per 64-bit word: the next occupied level is a find-first-set from the current index, so a 4096-tick band costs at most 64 word reads and typically one. Memory is 4096 x 8 bytes plus 512 bytes of bitset per side, about 32.5 KiB per side, 65 KiB per instrument.
- Price the alternative honestly. A sorted map is O(log L) per update with correct semantics and no band, but it allocates a node per new level on the decode path and chases pointers on every read; the disqualifier is the allocation and the cache misses, not the logarithm. Keep the flat array for the actively quoted set (1,000 instruments is about 65 MB) and a map for the long tail, because 10^5 instruments at 65 KiB each is 6.5 GB.
- Handle the band exit explicitly: when a price falls outside [base_px, base_px + width x tick], rebase by memmoving the live window and clearing the rest, an O(width) operation that must be counted and alarmed rather than hidden, since it is a latency spike correlated with fast markets.
- Tie it to the gap invariant: the book carries a staleness flag, and an unrecovered sequence gap sets it so best_bid()/best_ask() report unusable rather than returning the last well-formed state.
Follow-up
- Move from price-level to order-level (each resting order tracked individually). What does that cost you, and what does queue-position modelling need from it?
- The venue sends a snapshot plus increments after a gap. Where do you buffer the increments, and at which sequence number do you start applying them?
- How do you verify this book against an independent full-depth snapshot mid-session without stalling the decode thread?
Publish top instruments by traded notional every second
Fills stream in as (instrument_id, last_qty, last_px, contract_multiplier), with last_px an integer at the instrument's price_scale. Roughly 10^7 fills a day across 10^5 instruments. Publish the fifty instruments with the highest cumulative traded notional for the session, refreshed once a second, using exact integer arithmetic. Per-fill work must be O(1), and the per-second emit must not rescan all 10^5 instruments in the common case. Trade cancels and corrections can reduce an instrument's total. Give the accumulator width you need, and the argument that your pruning cannot drop a qualifying instrument.
Approach
- Size the accumulator before choosing the algorithm. A single fill of 10^6 lots at a price of 100 carried at price_scale 9 is 10^6 x 10^11 = 10^17 in scaled units, so an int64 session total overflows after about 92 such fills (9.22e18 / 1e17). Accumulate in int128, or rescale to a coarser notional unit at ingest and document the rounding; a double is out on the usual grounds, since it is exact only to 2^53.
- Per fill: one open-addressed hash map lookup on instrument_id, one 128-bit add, and an insert into a dirty set — O(1) expected, no allocation, no ordering work at all between emits.
- Per emit: maintain tau, the current fiftieth-largest total. Candidates are the previous top fifty plus the dirty instruments whose total exceeds tau. Select with a bounded min-heap of size 50, which is O(c log 50) for c candidates, and c is a few thousand per second rather than 10^5.
- State the precondition that makes the pruning sound: while totals only increase, an untouched instrument below tau cannot have crossed it, because nothing changed its value. Cancels and corrections break exactly that precondition, so a corrected instrument must enter the dirty set and, if it falls out of the top fifty, the replacement may be an instrument the pruning skipped.
- Close that hole rather than ignoring it: keep a reserve of the top 200 and refill from it when a member drops, falling back to a full O(m) quickselect over all 10^5 totals when the reserve is exhausted. The fallback runs rarely, is bounded, and is the reason the output is correct rather than usually correct.
- Report both costs: amortised O(1) per fill, O(c log k) per emit with an O(m) worst case, and note that the emit runs on its own thread off a snapshot so it never sits in the fill path.
Worked solution 30 min
- Compute the overflow point by hand for the worst realistic fill (qty 10^6, px 100 at price_scale 9) and confirm the count at which an int64 total wraps.
- Build a fixture of 20 instruments with k = 3, feed fills so totals are 100, 90, 80, 70, ... and record tau after the first emit.
- Send a fill of size 5 to the instrument at 70, taking its running total to 75, and confirm it is pruned without entering the heap because 75 is still below tau; then send a further fill of size 20 to that same instrument, taking it to 95, and confirm it now enters the heap and displaces the instrument at 80.
- Send a trade cancel that removes 30 from the instrument at 100 and confirm the reserve supplies the replacement rather than the pruned set being consulted.
- Drain the reserve deliberately with a run of cancels and confirm the quickselect fallback fires and returns the same answer as a brute-force sort.
Follow-up
- Make it a rolling five-minute window rather than a session total. What state does each instrument now need, and what does that do to memory at 10^5 instruments?
- Two instruments tie exactly at the fiftieth place. What is your tie-break, and why does an unstable one make the output non-reproducible in replay?
- The totals are in mixed currencies. Where does the FX conversion go, and what happens to the ranking when a rate updates mid-second?
Derive end-of-day positions from executions including trade corrections
You have position_snapshot rows with snapshot_kind = 'sod_clearing' (account_id, instrument_id, business_date, net_qty) and execution_report (PRIMARY KEY (venue_mic, venue_exec_id), order_id, instrument_id, account_id, exec_type, last_qty, last_px, corrects_exec_id, source, venue_ts, received_ts). Write the query producing eod_derived net_qty per account and instrument for one business date. exec_type includes trade_correct and trade_cancel, which arrive as new rows referencing an earlier execution; the original row is retained. Side lives on order_event, not here. Then say why avg_cost_px cannot be computed in the same statement.
Approach
- Get side without fanning out: join each execution to the single order_event row with event_type = 'submit' for its order_id. Joining on order_id alone multiplies every fill by the number of lifecycle rows that order accumulated, which is five or more on a partially filled order.
- Reduce corrections to an anti-join instead of arithmetic. Exclude any execution whose (venue_mic, venue_exec_id) appears as the (venue_mic, corrects_exec_id) of a later row: a superseded fill then contributes nothing, a trade_cancel contributes nothing itself, and a trade_correct contributes its restated last_qty once. Match on venue_mic too, since venue_exec_id is only unique within a venue.
- Sum CASE WHEN side IN ('buy','buy_to_cover') THEN last_qty ELSE -last_qty END over exec_type IN ('partial_fill','fill','trade_correct'), grouped by (account_id, instrument_id), then FULL OUTER JOIN onto the sod_clearing rows with COALESCE on both sides so an instrument traded from flat and one closed to flat both appear.
- Scope the date by the venue's business date, not by received_ts::date in the server's timezone. A correction for yesterday arrives today and belongs to yesterday.
- Do not dedupe in the reader. The primary key already makes a resend from drop_copy or the clearing file a no-op at write time (ON CONFLICT DO NOTHING), so a DISTINCT in the query is either redundant or hiding a schema problem.
- Answer the avg_cost_px question precisely: average cost depends on the running position at the moment of each fill, because a reducing trade leaves it unchanged and realizes P&L while a sign flip resets it. That is a sequential fold with carry, not an aggregate. SUM(...) OVER (ORDER BY ...) gives running quantity but cannot carry the average, so it needs a recursive CTE or an application-side fold.
Worked solution 35 min
- Build a fixture with one account and two instruments: a buy filled in three partials, a sell, a trade_correct restating one partial downward, and a trade_cancel of another fill.
- Write the anti-join that removes superseded executions and confirm on the fixture that the corrected partial's original row drops out.
- Fold onto the sod_clearing baseline with a FULL OUTER JOIN and COALESCE both sides to zero.
- Re-run after attempting to insert the same execution rows a second time with ON CONFLICT DO NOTHING, and diff the two result sets.
- Compute the watermark as the last applied row ordered by (received_ts, venue_exec_id) and confirm it is stable across re-runs.
Follow-up
- A resent report for an execution already inserted from the trading session arrives on the drop copy. What does your write path do, and what does the reader have to change?
- Make this incremental so the intraday_derived row refreshes every few seconds without rescanning the day. What does the watermark have to be ordered by?
- The derived total differs from eod_reconciled by 100 shares in one instrument. What do you do to the derived row?
Denormalising instrument attributes onto the position snapshot
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?
How do you approach debugging a production issue in a distributed syst…
How do you approach debugging a production issue in a distributed system?
Approach
- 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.
- Name the read and write paths separately; they rarely have the same bottleneck.
Follow-up
- What would you drop to keep the system up under load?
- What breaks first when traffic grows ten times?
How do you ensure your code remains maintainable as a project scales i…
How do you ensure your code remains maintainable as a project scales in complexity?
Approach
- State the consistency you need, and where you are willing to be stale.
- Fix the scope first: who calls this, how often, and what they do when it fails.
- 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?
Walk me through the design of a RESTful API for a data-intensive servi…
Walk me through the design of a RESTful API for a data-intensive service.
Approach
- Say what you would check first and why it is the highest-information step.
- Work from the requirement backwards to the design.
- Clarify what is being asked and what a complete answer contains.
Follow-up
- What assumption would you test first?
- How would you know your answer was wrong?
Serve as-of instrument definitions to every service on the path
instrument_version is effective-dated (SCD2), keyed by instrument_id plus effective_from and effective_to, carrying venue_mic, venue_symbol, tick_size, lot_size, price_scale, contract_multiplier, trading_status and expiry_date. Every service resolves through it: the order path reads price_scale and tick_size on every order at 10,000 orders/second per process, and the replay engine must resolve a symbol exactly as the live session did months earlier. Writes are a nightly corporate-action load plus intraday vendor corrections. Design the read path: cache shape and key, how a correction reaches processes already holding a snapshot, and what invalidation rule you enforce.
Approach
- Separate the two read patterns instead of building one lookup API. The order path needs an as-of-now read in nanoseconds with no network hop. Replay needs an as-of-T read, resolving the version whose [effective_from, effective_to) window contains T, and can afford one lookup per instrument at run start. Both are materialised from the same rows by one loader.
- Shape the hot-path cache as a process-local array indexed by a dense internal instrument index, resolved from symbol once at subscription time. At 10,000 to 1,000,000 instruments and roughly 100 bytes per definition that is 1 MB to 100 MB, which fits in RAM; a direct array index costs single-digit nanoseconds where a hash lookup on a symbol string costs tens and touches more cache lines.
- Invalidate by version rather than TTL. The loader builds a complete immutable snapshot with a monotonic snapshot_id and the reader swaps a pointer to it, so no reader ever observes a half-applied corporate action. This falls out of the storage model anyway: SCD2 rows are closed and superseded, never edited, so a correction is always a new snapshot.
- Pin replay to the snapshot the live session used by recording snapshot_id on strategy_run. A vendor correcting yesterday's tick size today will otherwise change a replay's output with no code change, and the divergence gets attributed to whatever was edited most recently.
- State the trade-off: preloading the full active set costs memory and a slow process start, and a lazy per-symbol fetch costs a network round trip inside the order path. Take the memory, and size it rather than assuming it.
Worked solution 20 min
- Load 200,000 instrument_version rows covering 50,000 instruments with several versions each, add the non-overlap constraint (in PostgreSQL, EXCLUDE USING gist (instrument_id WITH =, tstzrange(effective_from, effective_to) WITH &&) with btree_gist installed), then try to insert an overlapping version.
- Time three resolutions over the same data: the as-of SQL query with a range predicate, a process-local hash lookup keyed by the symbol string, and a direct array index by dense instrument index.
- Swap the snapshot pointer while a reader loop is running and measure the reader's pause and the version stamped on each read.
- Resolve one symbol that was reassigned mid-year, once as of March and once as of today.
Follow-up
- A vendor corrects a tick size for a date three months back. Which research runs are now unreproducible, and how do you enumerate them?
- An instrument is halted at 10:00:00. How many milliseconds until every gateway refuses new orders in it, and what happens to the orders already in flight?
- Where does the price_scale integer convention break down, and what would you do for an instrument quoted in fractions?
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.
Approach
- 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-up
- 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?
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.
Prepare, practise & reflect
One practical outcome each day. Spend longer where you need it.
0 / 7 done01Numbers 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.
Practice prompt ↗Practice prompt ↗Worked solution ↗02One 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.
Practice prompt ↗Practice prompt ↗03The 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.
Practice prompt ↗Practice prompt ↗04Failure 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.
Practice prompt ↗Practice prompt ↗Worked solution ↗05Scaling 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.
Practice prompt ↗Practice prompt ↗06Keep 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.
Practice prompt ↗Practice prompt ↗07Defend 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.
Practice prompt ↗Worked solution ↗Expand any day for tasks and deliverables. Your progress is saved on this device.
Team size, service count and tickets closed say very little. Seniority shows in the decision you owned: what you chose not to build, which constraint you traded away, whose objection you had to resolve before anything could move. A large project where you executed someone else's plan is a small story.
How do you handle authentication and state management in a Frontend ap…
How do you handle authentication and state management in a Frontend application?
Approach
- Pick a story where you made the decision, not one where you watched it.
- Give the blast radius: what could have broken, and what you measured.
- State the situation in two sentences and spend the rest on the reasoning.
Follow-up
- What would you do differently if you ran that again?
- How did you know your change caused the improvement?
Argue against a fast path around the pre-trade risk check
A respected engineer proposes skipping the pre-trade limit check for orders below a quantity threshold, to cut roughly three microseconds from the tick-to-trade path. The case is real: the desk is losing queue position. You think it is wrong and you have been told to build it. Describe a time you argued against a design you were assigned. State the failure you predicted, the evidence you brought, how long the disagreement ran, what you did once the decision went against you, and what production eventually showed. Include the version of your argument that persuaded nobody.
Approach
- Establish the technical failure before reaching for authority. A per-order quantity threshold bounds nothing that matters: small orders sum, and the limits a small-order bypass defeats are exactly the aggregate ones — max_position_qty, max_gross_notional, max_message_rate. Put arithmetic on it: threshold quantity times the achievable message rate times the minutes until a human notices.
- Grant the strongest form of their case rather than attacking the weakest. Three microseconds is real money on a queue-position strategy, so do not dispute the benefit. Dispute that the check is where the three microseconds are, and bring a profile instead of an opinion.
- Make the evidence cheap for them to verify: p99 of the check in isolation from a high-dynamic-range histogram, the number of limit rows actually scanned per evaluation, and the same path with the check removed on the same harness. A few hundred preloaded rows read from a flat immutable snapshot is typically a microsecond or less; the rest is often an allocation, a map lookup or a log line sitting next to it.
- State the alternative with its cost owned: keep the control non-bypassable and make it cheaper — a version-swapped immutable snapshot read through a single pointer, no allocation, no lock on the path — and accept that a limit change becomes a publish rather than an in-place edit, and that each evaluation must record the limit_version it read.
- Raise the regulatory point last and separately. In most regulated markets a non-bypassable pre-trade control is a legal requirement, so the fast path is not a latency trade-off anyone is entitled to make. Leading with it reads as an appeal to authority and loses the room before the engineering argument is heard.
- Describe disagree-and-commit concretely: what you built, what you instrumented so the prediction could be checked, and what measurement would have proved you wrong. A strong answer is falsifiable; a generic one says 'I raised concerns and moved on'.
Follow-up
- A limit tightens at 10:00:00.000 while an order approved at 09:59:59.999 is in flight. Which version applies, and how do you prove that months later?
- What measurement would have changed your mind about the three microseconds?
- How do you make the check cheap without letting it serve a stale limit?
Resolve a review disagreement over floating-point prices
A pull request stores avg_cost_px as a double and checks it against a tick boundary with a tolerance of 1e-9. The author notes that every test passes and the values are small. You think it should be an integer at the instrument's price_scale. Describe a code review disagreement you resolved. State the argument you made, the evidence you produced inside the review rather than after it, how long it took, and how it ended — including the case where you were the one who changed position.
Approach
- Lead with a reproduction, not a principle. Four lines that accumulate 0.01 a hundred times and print a value that is not 1.0 settle more than a paragraph, because IEEE-754 binary64 has no exact representation of 0.01.
- Explain the author's own evidence rather than dismissing it. Binary64 represents every integer exactly up to 2^53, about 9.0e15, so a scaled value carried in a double is exact until magnitudes grow — which is why the tests pass and why the first failure is a large notional in a low-priced instrument, in production, months later.
- Name the alternative and what it buys beyond exactness: an integer at the instrument's price_scale, with tick_size expressed at that same scale, turns the boundary check into px % tick_size == 0 and deletes the 1e-9 constant that nobody in the room can derive.
- Scope the change honestly instead of expanding the review. It is not one column — it is every boundary that reads it and a migration for rows already written. Say whether you asked for it in this pull request or filed it with an owner.
- State your stopping rule. A review is not a place to win: an unresolved correctness point goes to a third person or a written decision, not to another round of comments, and a strong answer names the round at which it escalates.
Follow-up
- The author counters that NUMERIC in PostgreSQL is exact, so why not use that end to end?
- Where in this system is a double still the right choice?
- How would you detect the drift in rows already written before you migrate them?
- 01
How do you handle authentication and state management in a Frontend application?
- 02
A respected engineer proposes skipping the pre-trade limit check for orders below a quantity threshold, to cut roughly three microseconds from the tick-to-trade path. The case is real: the desk is losing queue position. You think it is wrong and you have been told to build it. Describe a time you argued against a design you were assigned. State the failure you predicted, the evidence you brought, how long the disagreement ran, what you did once the decision went against you, and what production eventually showed. Include the version of your argument that persuaded nobody.
- 03
A pull request stores avg_cost_px as a double and checks it against a tick boundary with a tolerance of 1e-9. The author notes that every test passes and the values are small. You think it should be an integer at the instrument's price_scale. Describe a code review disagreement you resolved. State the argument you made, the evidence you produced inside the review rather than after it, how long it took, and how it ended — including the case where you were the one who changed position.
Is this an official McKinsey Quantumblack interview guide?
No. It is PracHub's own research and practice material for the Software Engineer role at McKinsey Quantumblack. Rounds and questions reflect what candidates have reported, not a process McKinsey Quantumblack has published, and they change over time. Confirm the current format and scope with your recruiter.
PracHub interview research ↗How much time should I spend preparing?
Most successful candidates dedicate several weeks to deep-diving into SQL and core Data Structures. Focus on quality of practice—solving fewer problems with a deep understanding of the "why" is more effective than rote memorization.
PracHub interview research ↗What is the most common reason for rejection?
Often, it is not a lack of technical knowledge but a failure to communicate during the Pair Programming stage. Ensure you are vocal about your approach and open to pivoting when receiving guidance from the interviewer.
PracHub interview research ↗Is there a specific focus on algorithms?
You will face 1–2 data structure and algorithm questions, but the focus is on practical application. Be ready to explain how your chosen algorithm would perform in a real-world, data-heavy production environment.
PracHub interview research ↗What is the culture like at McKinsey Quantumblack?
It is a high-performance, collaborative culture that values intellectual curiosity and ownership. You will be expected to contribute to complex projects from day one, so showing a proactive, "can-do" attitude is essential.
PracHub interview research ↗Sources & methodology 3 sources ↗
No official company page is cited. Rounds and questions come from candidate reports and PracHub editorial material; each source shows the date it was read.
- 01PracHub interview research ↗
PracHub editorial research into this company and role, maintained with this guide. Candidate-reported, not an employer publication.
PracHub page · Accessed 2026-09-22 - 02PracHub Software Engineer practice ↗
Cross-company practice questions for this role.
PracHub page · Accessed 2026-09-22 - 03PracHub interview preparation framework ↗
The framework the preparation plan follows.
PracHub page · Accessed 2026-09-22