Interview guides · iQuanti
iQuanti Software Engineer Interview Questions & Guide 2026
iQuanti software engineer prep on databases: indexes, query plans, and the anomalies an isolation level still permits.
The 60-second brief
A Software Engineer at iQuanti sits at the intersection of high-scale data processing and digital marketing innovation. You are not just writing code; you are building the infrastructure that powers sophisticated analytics, search engine optimization tools, and enterprise-level marketing platforms. The work is inherently complex, requiring you to balance the immediate needs of performance-driven marketing campaigns with the long-term architectural stability required for global scale.
The behavioural round is a technical round in narrative form. Prepare it by collecting specifics you actually owned, such as a design you argued against, an incident you diagnosed, or a decision you later reversed, rather than by rehearsing phrasing.
Focus areas
- Treat a send timeout as unknown, not failed
- Fold execution reports idempotently on venue exec id
- Detect sequence gaps before pricing off a book
Overview
In this role, you will contribute to products that help Fortune 500 companies make data-driven decisions. Whether you are optimizing algorithmic efficiency to handle massive datasets or developing responsive front-end interfaces, your output directly impacts the bottom line for high-profile clients. You will work within agile teams that value technical precision, logical problem-solving, and the ability to translate ambiguous business requirements into robust, deployable software.
Expect a fast-paced environment where your technical contributions are highly visible. iQuanti places a premium on engineers who can demonstrate deep mastery of their primary stack while remaining adaptable enough to pivot when business requirements shift. The role is challenging, rewarding, and deeply rooted in the practical application of computer science to real-world business problems.
The iQuanti interview process
iQuanti candidates report 2 rounds · ≈ 2-4 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.
Technical Discussions
What this round decides is narrow: whether you can produce code that runs and is correct on inputs nobody showed you. An elegant solution that does not compile scores below a plain one that does, so write a correct brute force first, say out loud that you know its cost, and improve it with the working version still on screen. What separates strong answers is who finds the broken case. Trace your own code against an empty input, a single element, and duplicate keys before you say you are finished, because being told is far more expensive than noticing.
What to demonstrate
- Whether degenerate inputs get checked without being asked for: an empty collection, one element, every element equal, and the extreme value the input type allows
- Whether the complexity you state matches the code you actually wrote, including a sort or a copy sitting inside a loop
- Whether the finished answer is verified against the worked examples before you call it done, rather than assumed correct because the code reads correctly
How to prepare
- Take five problems you have already solved and, without running anything, write down what each returns for empty input, a single element, and all-duplicates. Then run them and count how many you predicted wrong.
- Drill the brute force as its own skill: on ten problems, write only the obviously-correct slow version and time how long it takes to get it passing. If that is more than a few minutes, that is what to practise, not the optimal version.
- Add a fixed last step before you submit anything, reading only the loop bounds and the initial value of each accumulator, which is where most off-by-one errors live
Where candidates lose points
Representing prices and quantities as binary floating point.
IEEE-754 binary64 cannot represent 0.01 exactly, so a cost basis accumulated in doubles drifts, and an equality test against a tick boundary fails unpredictably. The fix is an integer at a fixed scale - a count of ticks, or a scaled integer at the instrument's price_scale - which also turns the tick-size check into a modulus rather than a tolerance comparison. The nuance that lets this trap survive code review: binary64 represents every integer exactly up to 2^53, about 9.0e15, so a scaled integer carried in a double is exact right up until it is not, and the first failure tends to be a large notional in a low-priced instrument, in production.
Retrying an order send after a timeout, on the assumption that the send failed.
A timeout says nothing about whether the venue received, matched and acknowledged the order - only that a reply did not arrive in time. Retrying turns an unknown into a duplicate: two live orders, twice the intended position, and a hedge computed against a position record that is now wrong. The correct move is to treat the client order id as an idempotency key, persist it before the send, and on recovery query state (order status request, or the drop-copy stream) rather than resend. It is the same shape as a double-captured payment, except the second order can move the price against you while you work out what happened.
Treating a network call as though it were a local function call
A remote call can be slow, fail, or return after you stopped waiting, so name the timeout, the retry policy, and what the caller sees while the dependency is down. A call with no timeout turns one slow dependency into an exhausted thread or connection pool in every service upstream of it.
Starting work without saying what you are about to spend time on
State the plan before executing it: the approach, roughly how long it will take, and what you intend to leave hand-waved. That gives the interviewer a chance to redirect you in ten seconds rather than watching you spend fifteen minutes on the wrong sub-problem.
Questions & practice
What are the common performance pitfalls when working with arrays and strings?
Medium · data structures and algorithms
Approach
- Restate the input: its shape, its size, and what is guaranteed about it.
- State the target complexity and say which constraint rules the naive version out.
- 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?
Explain how you would use a multi-threaded application to read or write from a file for faster operations.
Medium · data structures and algorithms
Approach
- Walk one small example through your approach before writing the whole thing.
- Choose the data structure from the access pattern, not from familiarity.
- Restate the input: its shape, its size, and what is guaranteed about it.
Follow-up
- What is the worst case, and how likely is it on real data?
- How does this change if the input no longer fits in memory?
How do you reduce code complexity when refactoring legacy modules?
Medium · data structures and algorithms
Approach
- Restate the input: its shape, its size, and what is guaranteed about it.
- Choose the data structure from the access pattern, not from familiarity.
- Walk one small example through your approach before writing the whole thing.
Follow-up
- Which test case would catch an off-by-one here?
- How does this change if the input no longer fits in memory?
Write a function to print a spiral matrix.
Medium · data structures and algorithms
Approach
- Restate the input: its shape, its size, and what is guaranteed about it.
- 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
- What is the worst case, and how likely is it on real data?
- Which test case would catch an off-by-one here?
Find every overlapping effective window before adding the constraint
Hard · interval sweep · skew · null semantics
instrument_version holds about 10^7 rows of (instrument_id, effective_from, effective_to nullable). Versions for one instrument_id must never overlap, and at most one may have effective_to NULL. Adding the EXCLUDE constraint aborts on the first conflicting pair it meets, so you need the whole picture first. Produce every row that overlaps another row for its instrument, per-instrument overlap counts, and every instrument holding more than one open-ended row. The median instrument has about ten versions; the worst has 10^5 after repeated vendor reloads. Explain why the pairwise comparison is correct but cannot ship.
Approach
- Cost the naive version properly rather than dismissing it. Grouped by instrument_id it is the sum of g^2 over groups, and at a median g of ten that is trivial — the blow-up lives entirely in the tail, where one 10^5-version group contributes 5 x 10^9 comparisons. Worse, its output is also quadratic: if that group is a repeated reload, nearly every pair overlaps, so the self-join emits billions of rows describing a few thousand bad versions. The output size, not the runtime, is the reason it cannot ship.
- Restate the deliverable as rows rather than pairs, which is what makes a linear output possible, then sort by (instrument_id, effective_from, effective_to nulls last) for O(n log n) and sweep each group once.
- In the sweep, carry running_max_end = MAX(COALESCE(effective_to, infinity)) over the rows already seen in this group, plus the row that set it. A row overlaps iff its effective_from is strictly less than running_max_end; flag both that row and the max-setter, so both sides of every overlap appear in the output. O(1) extra state per group.
- Do not shortcut to comparing adjacent rows only. It is sufficient to answer the boolean does any overlap exist — if every adjacent pair is disjoint then end_i <= start_(i+1) <= start_j for all j > i, so nothing overlaps — but it misses nested intervals when you must list every offending row, and a long open-ended version nesting later ones is the single most common real shape.
- If you write it in SQL, the window-function form is MAX(COALESCE(effective_to, 'infinity'::timestamptz)) OVER (PARTITION BY instrument_id ORDER BY effective_from ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING). The COALESCE is load-bearing, and in two distinct ways: MAX ignores NULLs, so an earlier still-open version contributes nothing to the running maximum, and where it is the only preceding row the maximum is NULL outright, which makes effective_from < running_max evaluate to NULL rather than true. Either way the comparison is not true and the overlap disappears from the report.
- Handle the open-row rule as its own aggregate in the same pass — COUNT(*) FILTER (WHERE effective_to IS NULL) > 1 per instrument — since a second open row is a violation whether or not your range model makes it overlap, and since it is the only part of the report that survives the COALESCE being dropped. Only then add EXCLUDE USING gist (instrument_id WITH =, tstzrange(effective_from, effective_to) WITH &&), which needs btree_gist for equality on a bigint, and expect to iterate because each failed build names one pair.
Worked solution (35 min)
- Build instrument 7 with r1 = [2026-01-01, NULL), r2 = [2026-03-01, 2026-04-01), r3 = [2026-05-01, 2026-06-01), and instrument 9 with two rows both open from 2026-01-01 and 2026-02-01.
- Run the sweep with COALESCE to infinity and record which rows are flagged for each instrument.
- Run the same sweep with plain MAX(effective_to) and diff the two outputs, evaluating the comparison for r2 and r3 separately.
- Run an adjacent-pairs-only variant and diff again.
- Attempt to add the EXCLUDE constraint on the unfixed fixture and record what it reports.
Expected result: The correct sweep flags all three rows of instrument 7 (r2 and r3 both nest inside r1's open window) and both rows of instrument 9, and lists instrument 9 in the multiple-open-rows output. Plain MAX flags nothing at all for either instrument: over r2's preceding frame the maximum is MAX(NULL) = NULL, so 2026-03-01 < NULL is NULL rather than true; over r3's frame it is MAX(NULL, 2026-04-01) = 2026-04-01, and 2026-05-01 is not less than that; instrument 9's second row compares against MAX(NULL) = NULL too. The only thing the plain-MAX report still surfaces is instrument 9, and only via the separate open-row count. Adjacent-only, with the COALESCE kept, catches r1/r2 but misses r3, which nests inside r1 and does not touch r2. The constraint build fails naming exactly one pair.
Verify it yourself
- On a small fixture, the sweep's flagged-row set equals the set produced by a brute-force O(g^2) pairwise comparison — same rows, regardless of pair count.
- Every row flagged has a named counterpart row that it actually overlaps, checkable by hand from the two ranges.
- After the flagged rows are repaired, the EXCLUDE constraint builds on the first attempt; if it does not, the report was incomplete rather than the data newly broken.
Follow-up
- tstzrange(a, a) is empty and empty ranges never overlap, so a zero-width version passes the constraint. Is that acceptable, and what would catch it?
- The fix requires closing thousands of rows. How do you do that without editing history that a replay depends on?
- How would you run this check continuously, so the next bad vendor load is caught at write time instead of at constraint-build time?
Why the execution-report lookup stopped using its composite index
Medium · indexing · sargability · query-planning
execution_report holds 4 x 10^9 rows with an index on (account_id, received_ts). The query WHERE account_id = $1 AND received_ts::date = $2 runs a sequential scan. A second query, WHERE venue_exec_id LIKE $1 || '%', also scans despite a btree on venue_exec_id VARCHAR(48) in a database with a non-C default collation. A third, WHERE account_id = $1 alone, scans and the planner is right to. Explain each, give the rewrite for the first (a business date is a venue-local concept, not a server-timezone one), and say what EXPLAIN output you would ask for.
Approach
- First query: received_ts::date wraps the indexed column in a function, so the index's second column can no longer bound a range and only the account_id prefix is usable. If that prefix is not selective, a sequential scan is the cheaper plan and the index was never going to help.
- Get the rewrite's boundaries right rather than just removing the cast. Casting timestamptz to date depends on the session's TimeZone setting, so the same query means different instants in different sessions. A business date is defined by the venue's calendar, so take the session open and the next open from that calendar and write received_ts >= $open AND received_ts < $next_open.
- If the cast semantics are genuinely wanted, index the expression: (received_ts AT TIME ZONE 'UTC')::date is immutable because the zone is a literal, so it can be indexed, while the bare ::date is only stable and PostgreSQL will refuse it. The cost is pinning the zone into the index definition.
- Second query: a default-collation btree cannot serve a prefix LIKE, because the collation's sort order is not the byte order the pattern match needs. Rebuild it with varchar_pattern_ops (or run the database in the C collation), and keep the plain index too if equality lookups still need collation-aware comparison.
- Third query: an index scan returning a large fraction of the table reads most of the heap in random order on top of the index, so the sequential scan is correct. The fix is a different layout, such as partitioning by session_date or a BRIN on received_ts over a naturally time-clustered table, not a different btree.
- Ask for EXPLAIN (ANALYZE, BUFFERS) and compare estimated against actual rows. A two-orders-of-magnitude estimate error points at stale or correlated statistics, which CREATE STATISTICS or a composite index can fix; an accurate estimate that still chooses a scan means the scan is the right plan and the question is wrong.
Follow-up
- The rewritten range query is still slow because one account-day is 200 million rows. What changes about the physical design?
- Before dropping an index you believe is unused, how do you establish that in production?
- When does BRIN on received_ts beat the composite btree here, and what breaks that assumption?
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.
Worked solution (25 min)
- Measure the current as-of join with EXPLAIN (ANALYZE, BUFFERS) over 50,000 positions: record which join method the planner actually chose, how many version rows the scan discarded through the effective-dating filter, and the estimated-versus-actual rows on that filter.
- Build the per-business-date dimension with CREATE TABLE AS from the as-of query, and add UNIQUE (instrument_id, business_date).
- Re-run the dashboard query as an equality join against the dimension and compare timing, plan shape and the size of the side being scanned.
- Simulate a mid-session contract_multiplier change on one instrument and inspect what the dimension returns for it.
Expected result: The scanned side shrinks from every stored version to the one row per instrument in effect that date, the effective-dating filter quals and the misestimate that can flip the plan into a per-row nested loop both disappear, and the resolution stops being recomputed on every refresh. The mid-session contract_multiplier change is visibly wrong in the per-date dimension, which is the reason to handle those instruments separately rather than a reason to abandon the design.
Verify it yourself
- Dimension row count equals the number of instruments with a version in effect on that date.
- Every position row joins to exactly one dimension row; a LEFT JOIN producing NULLs means a missing instrument, not an empty desk.
- The dimension carries the effective_from it resolved from, and three spot-checked rows match instrument_version.
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?
What are the best practices for structuring a JavaScript codebase for scalability?
Medium
Approach
- Work from the requirement backwards to the design.
- 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.
Follow-up
- How would you know your answer was wrong?
- What assumption would you test first?
Signed drop-copy webhooks with at-least-once delivery
Medium · webhooks · idempotency · authentication
A clearing broker pushes execution reports to your HTTPS endpoint rather than letting you poll. Each POST carries venue_mic, venue_exec_id, exec_type, cum_qty, leaves_qty and corrects_exec_id, plus signature and timestamp headers. Delivery is at-least-once and unordered, and any non-2xx is retried with backoff. Specify signature verification, the accepted timestamp window, what you return and how quickly, and how the handler stays correct when the same execution arrives twice and when a trade_correct arrives before the fill it corrects.
Approach
- Verify over the raw request bytes before parsing: HMAC-SHA256 over timestamp, a separator, and the body exactly as received, compared in constant time. Parsing and re-serializing JSON changes key order and whitespace and will fail a signature that is actually valid, so the framework must hand you the buffered body.
- Support two active secrets at once so rotation is an overlap rather than an outage, and accept a signature matching either.
- Bound replay with a timestamp tolerance (a few minutes, sized to your clock-skew budget plus the sender's retry backoff): outside it, reject. This bounds how long a captured request stays useful; it does not replace idempotency, because redelivery inside the window is legitimate.
- Acknowledge fast and apply asynchronously: return 2xx once the raw message is durably enqueued, not once it is folded into a position. The sender's retry timer is shorter than your downstream pipeline, and a non-2xx means 'send it again', so never return 500 for a message you have already accepted.
- Make the sink idempotent on (venue_mic, venue_exec_id) with an upsert, and make it order-insensitive by taking the venue's cum_qty and leaves_qty as authoritative rather than incrementing a local counter. Unordered delivery then costs nothing.
- Handle corrections as arrivals, not edits: a trade_correct or trade_cancel names corrects_exec_id, which may reference a row you have not received. Store it pending and resolve on arrival of the original, and keep the original row so the fold sees a negating entry instead of an in-place update.
Worked solution (30 min)
- Write the verification function signature showing it takes raw bytes plus headers, and the constant-time comparison call.
- Write the handler as ordered steps with the exact point at which 2xx is returned, and label what has been made durable by then.
- Write the upsert on (venue_mic, venue_exec_id) and trace two deliveries of the same report through it.
- Trace a trade_correct arriving before its original and write the pending-resolution rule.
- List the status codes you return and what each instructs the sender to do.
Expected result: A handler that authenticates on raw bytes with rotatable secrets, rejects stale timestamps, returns 2xx only after durable enqueue, and whose sink is idempotent on (venue_mic, venue_exec_id) and insensitive to arrival order because it takes the venue's cumulative quantity as authoritative and stores corrections as new negating rows.
Verify it yourself
- Replay a captured request an hour later and confirm rejection; replay it ten seconds later and confirm it is accepted and folds to no change.
- Deliver a report stream shuffled and duplicated; the resulting positions must equal the in-order, deduplicated run.
- Confirm that no code path returns a non-2xx after the message has been made durable.
Follow-up
- The sender is down for two hours. How do you detect that, given that no request arriving looks exactly like no executions happening?
- Your endpoint returns 200 but the enqueue silently drops. What end-of-day control catches it?
- Would you let the sender's payload carry your position directly rather than the execution? What breaks?
Order gateway heap grows only on reconnect days
Medium · memory leak · heap profiling · order lifecycle
The order gateway's resident set grows about 400 MB a day, masked by a nightly restart. On a day with three session drops it grew 6 GB in an hour and was OOM-killed mid-session. Order state is indexed two ways: a map keyed by order_id and a map keyed by client_order_id, and client_order_id changes on every replace. Give an ordered checklist that separates a real leak from resident-set growth, attributes it to an allocation site, and names the fix and the regression test.
Approach
- Separate resident set from live heap before anything else. Memory freed to the allocator is not necessarily returned to the operating system, and transparent huge pages and per-thread arenas inflate resident set without live bytes. Compare resident set against the allocator's own accounting over the same window; if live bytes are flat, this is a retention or fragmentation question and a different investigation.
- Take two heap profiles an hour apart under representative load and diff them by allocation site rather than reading current totals. A profiler sorted by total size points at whatever is largest, which is rarely what is growing.
- Correlate growth with traffic shape rather than with elapsed time. Plot live bytes against submits, replaces and inbound execution reports. Growth proportional to replaces implicates the per-message index; growth proportional to reconnects implicates the resend path.
- Audit lifetime against the order state machine. Terminal state is a property of order_id, not of client_order_id: an eviction that fires once when an order reaches filled, canceled, rejected or expired removes exactly one client_order_id and orphans every earlier one the order used. Entries also survive indefinitely when a send times out and no terminal event ever arrives.
- Fix by bounding the per-message index's lifetime rather than its size. Keep it as a lookup from client_order_id to order_id, evict every id attached to an order when that order goes terminal, and give an unresolved send an explicit expiry that resolves through an order status request or the drop-copy stream instead of lingering.
- Add the test that would have caught it: replay a recorded session containing replaces and a mid-session disconnect, and assert both index sizes return to their pre-session values.
Follow-up
- A send times out and no terminal event ever arrives for that order. How long do you hold the entry, and what resolves it?
- Someone proposes an LRU on the client_order_id map. What does that turn the leak into?
- The heap profiler costs around 3% on the order path. Where and when do you run it?
Suggested practice checklist
Four days sample coding, design, fundamentals and the practical rounds at deliberately shallow depth, which is enough to surface the topics you did not know were in scope. That map, rather than a guess made on day one, decides where the last three days go.
Day 1 — Coding, one pass at shallow depth
- Solve one problem from each of six families, an array with two pointers, hash counting, binary search, a tree traversal, a graph traversal and one dynamic program, under a hard twenty-minute cap with no extensions, marking each finished, late, or stalled.
- For every stall, write the exact move you could not make rather than the subject, so the note reads could not turn the recurrence into a loop rather than bad at dynamic programming.
- Fix nothing today. The value of the pass is the unfixed record.
Deliverable: Six timed attempts marked finished, late or stalled, each stall carrying a named blocking move.
Day 2 — Design, one pass at shallow depth
- Spend twenty minutes each on three different shapes, a read-heavy feed, a write-heavy ingest path, and something needing a transaction across two entities, stopping each at requirements, interface and data model.
- After each, write the first question you could not answer, which is usually a number you could not estimate or a failure mode you had no vocabulary for.
- Mark which of the three you would be most relieved not to be asked, and treat that as data rather than as a preference.
Deliverable: Three shallow designs, each with the first unanswerable question written at the bottom.
Day 3 — Fundamentals and the practical rounds
- Answer eight short questions in writing at four minutes each, covering the material that fills the gaps between the big rounds: what happens between a URL and a rendered page, what an index costs on write, when a process is preferable to a thread, and what conditions a deadlock requires.
- Do one thirty-minute practical task of the kind a take-home compresses: read an unfamiliar two-hundred-line file and write what it does, what you would change, and the one thing you remain unsure of.
- Score every answer fluent, correct but slow, or absent, and keep the absent ones visible.
Deliverable: Eight scored short answers and one written reading of unfamiliar code.
Day 4 — The rounds that are about you, and the map
- Deliver three behavioural answers aloud against a timer, a conflict, a failure you owned, and a decision made without enough information, marking any that ran past three minutes or contained no number.
- Assemble the map: every marked item from days one to three on a single page, sorted by how likely it is to appear in your loop rather than by how uncomfortable it felt.
- Choose exactly two areas for the remaining three days and write down what you are deliberately abandoning.
Deliverable: A one-page scored map of the whole surface area with two areas chosen and the rest explicitly abandoned.
Day 5 — First chosen area, to the depth you skipped
- Work the higher-ranked area in four focused blocks, choosing items one level above where you stalled rather than repeating what already works.
- After each block write the rule you extracted in one sentence with its precondition attached, since a rule carrying no precondition is exactly what fails under a variation.
- Re-attempt the day-one or day-two item that exposed this area and compare against the original timing.
Deliverable: Four worked blocks, a timed re-attempt against the original, and three one-sentence rules with preconditions.
Day 6 — Second chosen area, where the gap is coverage rather than speed
- Treat the second area differently from the first. Day five drilled something you could already half-do; this one is usually a topic you had simply never met, so build one worked reference example end to end and keep it, rather than attempting six problems badly.
- Write down the vocabulary you were missing on day two or three, five terms at most, each with the one sentence that makes it usable in an answer rather than the textbook definition.
- Redo the shallow attempt that exposed this area and note whether you now fail later in the problem, because moving the failure point is the realistic gain from a single day and is worth more than a score that did not change.
Deliverable: One worked reference example for the newly covered area, a five-term vocabulary list, and a note on where the failure point moved.
Day 7 — Reassemble the loop
- Sit two rounds back to back with no gap, ordering them so the area you chose second comes last, because the map was built from rested, isolated attempts and the loop will reach your weaker area when you are already spent.
- Write where the second round suffered from the first, which is normally the point at which structure collapses into narration.
- Reduce the week to one page holding only the rules you can state without reading them.
Deliverable: Mock notes on cross-round carryover plus a one-page card of rules you can recite from memory.
The bug you decided not to fix
Engineers over-index on what they repaired. A stronger answer covers something you knowingly left broken: the alert you tuned down, the data inconsistency you documented instead of chasing, the cleanup you deferred past two quarters. Give the reasoning and the condition that would have reopened it, so it reads as a decision and not as neglect.
How do you handle complex state management in React applications?
Medium · behavioural and engineering judgement
Approach
- Give the blast radius: what could have broken, and what you measured.
- Name the disagreement and how you resolved it with evidence.
- Close with what you would do differently, concretely.
Follow-up
- What did you decide not to do, and why?
- How did you know your change caused the improvement?
How do you handle feedback on your code during peer reviews?
Medium · behavioural and engineering judgement
Approach
- State the situation in two sentences and spend the rest on the reasoning.
- Close with what you would do differently, concretely.
- Name the disagreement and how you resolved it with evidence.
Follow-up
- What did you decide not to do, and why?
- What would you do differently if you ran that again?
Own the postmortem for a stale-book quoting incident
Medium · incident response · sequence gaps · market data · blast radius
A sequence gap hit both the A and B lines of one venue channel at 09:31. Recovery was requested but never completed, the book stayed marked ok, and a strategy quoted against phantom levels for 14 minutes. Take the on-call role. Describe an incident you owned of comparable blast radius: how it was detected, how you bounded which instruments and orders were affected, what you stopped first, and what the loss was. Give a wall-clock timeline, the query that sized it, and the one change that would have caught it sooner.
Approach
- Open with the invariant that broke rather than the symptom: no order is priced off a book with a known, unrecovered gap. The invariant tells the listener what to count; 'we got bad fills' does not.
- Bound the population with a stated query, not an adjective. From feed_channel_session, the rows for that (venue_mic, channel_id, session_date) on both lines where unrecovered_msg_count > 0 give the window; join order_event on instrument_id where sent_ts falls between the first gap and the recovery. The finding that matters is that book_state read 'ok' throughout — the gap was the event, the unchanged state flag was the bug.
- Separate mitigation from fix and say which came first. Mitigation is blunt and needs no deploy: force the channel's book_state to stale, pull quotes, and cancel resting orders — resting orders keep trading while you are deciding, which is the part people forget. Making detection structural per line is not an incident-window change.
- Size the loss with a method attached: replay the capture, rebuild the book with the gap recovered, and remark the fills against that. State what you cannot attribute — some of the adverse selection in those 14 minutes would have happened anyway — because a loss number that claims everything is not believed.
- Name one detector with a threshold and a cost, not five. max_interarrival_us exceeding the venue's heartbeat interval catches a silent line; per-line sequence continuity catches a lossy one. Say which you shipped and its expected false pages per week.
- Name your own error inside the response window — the wrong hypothesis you held for 20 minutes, or the mitigation that made it worse. That is the part candidates rehearse away and interviewers weight most.
Follow-up
- Resting orders kept trading while you were deciding. What is the policy: cancel on stale, or leave them and stop adding?
- Recovery completed but the book could still be wrong. How do you prove a reconstructed book is correct before you quote off it again?
- What do you tell risk about the portion of the loss you cannot separate from ordinary adverse selection?