Interview guides · Bayview Asset Management
Bayview Asset Management Software Engineer Interview Questions & Guide 2026
Concurrency drills for Bayview Asset Management software engineer prep: races, lock ordering, and what a check-then-act sequence assumes.
The 60-second brief
A Software Engineer at Bayview Asset Management plays a pivotal role in bridging the gap between complex financial data and actionable technological solutions. In an industry defined by precision and speed, your work directly impacts the tools used for asset valuation, risk assessment, and portfolio management. By developing robust applications and maintaining critical infrastructure, you enable the firm to make informed, data-driven decisions that sustain its competitive edge in global markets.
Ask how many rounds there are and what each one is before you plan your weeks, because the composition is what your hours should follow. A loop with three coding rounds and one short design conversation deserves a different split from the reverse, and whoever schedules it will usually say plainly which it is.
Focus areas
- Reconcile derived positions against clearing every day
- Replay recorded input through the same build
- Detect sequence gaps before pricing off a book
Overview
This role is both challenging and intellectually stimulating, requiring a balance of technical rigor and a deep understanding of business requirements. You will work within a high-stakes environment where software reliability is paramount. Whether you are building full-stack interfaces or optimizing backend processes, your contributions are essential to the operational efficiency and strategic success of Bayview Asset Management.
Candidates should emphasize their ability to translate complex financial requirements into clean, scalable code, as the firm values engineers who understand the business impact of their technical decisions.
The Bayview Asset Management interview process
Bayview Asset Management candidates report 3 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.
Behavioral Discussion
What you say here is written down by each interviewer and compared afterwards, so the unit of evaluation is a claim someone else could check, not a well-told narrative. Two things make a story checkable: detail only a participant would hold, and a clean line around which part was yours. Vague ownership is the usual failure and it is usually accidental, because engineers say we about the team's work and we about their own, so the thing they personally built disappears into the plural. Name the part you wrote, and name who did the rest.
What to demonstrate
- Whether your details are ones a participant would hold and an observer would not: the constraint that ruled out the obvious approach, the first attempt that failed, the person who objected and on what grounds
- Whether ownership survives a direct question, since a follow-up to we decided is routinely who decided, and an answer that stays plural at that point is read as the work belonging to someone else
- Whether the numbers you quote are ones you would say identically to a former colleague with the dashboard open
How to prepare
- Go through each story replacing every we with either I or a named role (the on-call engineer, the reviewer, the other team) and check the story still holds together. Wherever it stops making sense you have found a part you cannot actually speak to
- Open the artefacts for two of your stories, the pull request, the design doc, the incident notes, and read them for dates and figures you have been rounding in the retelling. Correct your version to match
- For each story write the single sentence you would least want repeated to a former teammate, then either make it accurate or take it out
Final Onsite Discussions
Coding rounds mostly set a floor. They decide whether you clear the bar, not where you land on the ladder. Level tends to come out of the design discussion and the ownership stories, so the question worth auditing beforehand is whether the scope you describe matches the scope of the job. Work that stops at your own service, or a story whose hard part was writing the code rather than getting several people to agree on an interface, reads a level below where you think you are interviewing, and that gap is usually resolved downwards.
What to demonstrate
- Whether the largest thing you describe owning ran end to end — the decision, the migration path, the rollout, and what you did when it went wrong — or stopped at the change you merged
- Whether design answers include what you would not build, what you would defer, and what you would measure before committing, rather than only what the boxes are
- Whether a disagreement in a story was settled with something checkable — a benchmark, a prototype, a written proposal — instead of by seniority or by waiting it out
- Whether you can say which calls you made alone and which you escalated, and why the line sat where it did
How to prepare
- Write your largest piece of owned work as a timeline of decisions — who decided what, when, and what you did when the plan broke — then delete every sentence whose subject is "we" and see how much survives
- Take one system you know well and drill the migration answer: how old and new paths run side by side under live traffic, how you compare their outputs, what the rollback is once writes are going to both, and which step you would not automate
- Map each line of the ladder in the job posting to a specific thing you have done, find the line you cannot support, and prepare the closest evidence you have plus an honest account of the gap
Where candidates lose points
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.
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.
Hardcoding to the sample inputs
Solve the stated problem rather than the two examples; special-casing a literal to make a sample pass is obvious immediately and reads as either a misunderstanding or an attempt to fake progress. If you genuinely cannot generalise yet, say which part is a stub and what would replace it.
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.
Questions & practice
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?
Choose a book structure for constant-time top-of-book reads
Medium · data structure choice · cache locality · order book
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?
Attribute every order to the limit version in force
Medium · as-of join · two pointers · temporal data
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?
Derive end-of-day positions from executions including trade corrections
Medium · event-sourcing · idempotency · aggregation · corrections
You have position_snapshot rows with snapshot_kind = 'sod_clearing' (account_id, instrument_id, business_date, net_qty) and execution_report (PRIMARY KEY (venue_mic, venue_exec_id), order_id, instrument_id, account_id, exec_type, last_qty, last_px, corrects_exec_id, source, venue_ts, received_ts). Write the query producing eod_derived net_qty per account and instrument for one business date. exec_type includes trade_correct and trade_cancel, which arrive as new rows referencing an earlier execution; the original row is retained. Side lives on order_event, not here. Then say why avg_cost_px cannot be computed in the same statement.
Approach
- 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.
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?
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?
How do you ensure the scalability and reliability of the services you build?
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
- How would you know your answer was wrong?
- What assumption would you test first?
What is your approach to debugging performance bottlenecks in a production environment?
Medium
Approach
- Work from the requirement backwards to the design.
- Say what you would check first and why it is the highest-information step.
- State your assumptions explicitly before working the problem.
Follow-up
- How would you know your answer was wrong?
- What assumption would you test first?
Idempotent order submission under an ambiguous send timeout
Medium · idempotency · retries · http-semantics
Design the HTTP contract for POST /v1/orders, called by an execution management system that submits one order per request. Its socket times out at 250 ms and its retry policy fires immediately. order_event records client_order_id VARCHAR(40) (venue-unique, changes on replace), order_id BIGINT (stable across replaces), seq_no, status_after and received_ts. Specify the request body, the idempotency key with its scope and retention, the status codes for first accept, exact replay and key reuse with a different body, and exactly what the caller does when no response arrives at all.
Approach
- Make client_order_id the idempotency key and require the caller to allocate it, not the server: a server-generated key cannot deduplicate the retry of a request whose response was lost, because the retry would be given a fresh one.
- Persist (account_id, client_order_id, request_fingerprint) durably BEFORE the outbound venue send, so a crash between persist and send leaves a record to resolve against rather than a silent gap.
- Define the three outcomes: 201 with order_id on first accept; 200 replaying the stored response when the same key arrives with a matching fingerprint (a SHA-256 over the canonicalized body, so the server is not diffing JSON); 409 when the key repeats with a different fingerprint, which is a caller bug, not a retry.
- Implement the write as a single conditional insert (INSERT ... ON CONFLICT (account_id, client_order_id) DO NOTHING RETURNING event_id), so two concurrent retries race to one winner in one round trip instead of both passing a read-then-write check.
- State scope and retention explicitly: unique per (account_id, client_order_id), retained at least as long as the venue session plus the caller's maximum retry horizon. A key that ages out early converts a late retry into a second live order.
- Specify the no-response path as a separate operation: the caller MUST NOT resend; it calls GET /v1/orders?client_order_id=... or reads the drop-copy stream, and only acts once the venue's view is known.
Worked solution (30 min)
- Write the request body and response body for the happy path, marking which fields the caller supplies and which the server assigns.
- Write the persist-then-send sequence as numbered steps, and annotate each gap between steps with what a crash there leaves behind.
- Write the conditional-insert statement and trace two concurrent identical requests through it, naming which one gets 201 and which gets 200.
- Write the fingerprint rule: what is canonicalized, what is hashed, and what field difference must produce 409 rather than a silent accept.
- Write the caller's failure handling as pseudocode with three branches (2xx, 4xx, no-response), and check that the no-response branch contains no send.
Expected result: A contract in which the same client_order_id may be POSTed any number of times and at most one order ever reaches the venue: 201 on first accept, 200 replaying the stored response, 409 on fingerprint mismatch, and a documented status-query path that is the only legal response to receiving nothing.
Verify it yourself
- Delete the response on the wire for every request in a test run: the number of orders at the venue must equal the number of distinct client_order_ids, not the number of POSTs.
- Kill the server between the idempotency-record insert and the venue send; recovery must be able to classify that order as unknown rather than as absent.
- Resubmit a stored key with order_qty changed by one and confirm a 409, not a second order and not a silent 200.
Follow-up
- The process crashes after persisting the key but before the venue send. What does the recovery path do, and how does it tell that case apart from a send that succeeded?
- How do you expire idempotency records without reopening the duplicate window, and what does the endpoint return for a key that has aged out?
- A replace changes client_order_id but keeps order_id. Which of the two is the idempotency key for POST /v1/orders/{order_id}/replace, and why?
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
For someone who has spent the last few years shipping features and reading other people's code, and who has not solved a timed problem from a blank file in a long time. Five days rebuild the primitives and the patterns that sit on them, working from invariants rather than remembered solutions, and the last two attach that back to the rest of the loop.
Day 1 — Rebuild the primitives by implementing them
- Implement a dynamic array with doubling growth and an operation counter, then change the growth rule to add a fixed sixteen slots instead, and time both for n of ten thousand, a hundred thousand and a million. The fixed-increment version resizes n/16 times at O(n) each, so its total work is quadratic; doubling is what makes append amortised constant.
- Implement a hash map with separate chaining and a load-factor resize, then insert ten thousand keys engineered to land in one bucket and record what happens to lookup time, so that average-case O(1) becomes a claim with a stated precondition rather than a reflex.
- For dynamic-array append and hash-map insert, write down which cost is amortised rather than worst-case, which single operation pays the whole bill, and what a system with a hard per-operation deadline would have to do instead.
Deliverable: Two working implementations plus a timing table showing the input at which each structure's advertised complexity stops holding.
Day 2 — Arrays under an invariant: two pointers, sliding window, binary search
- Solve longest-subarray-with-sum-at-most-K using a sliding window, then run it on an input containing negative numbers and watch it return the wrong answer: extending the window only moves the sum monotonically when every element is non-negative, and that precondition is the whole reason the technique works.
- Write the binary search that finds the first index satisfying a predicate rather than an exact value, put the loop invariant above the loop in a comment, and verify termination on the two inputs that break careless versions: the empty range, and a range where every element satisfies the predicate.
- Compute the midpoint as lo + (hi - lo) / 2 and write one line on why the obvious (lo + hi) / 2 is a genuine defect in a fixed-width integer type and a non-issue in a language with arbitrary-precision integers.
Deliverable: Three solved problems, each with its invariant written above the loop, plus one recorded input on which the sliding window is provably wrong.
Day 3 — Sorting, heaps, and the greedy argument that has to be proved
- Solve one top-k problem three ways, by full sort, by a size-k heap, and by quickselect, then write the values of n and k at which each becomes the right choice, along with quickselect's quadratic worst case and why a randomised pivot makes that unlikely rather than impossible.
- Implement bottom-up heapify and count sift-down steps to confirm it does linear work rather than n log n, because most nodes sit near the bottom of the tree and therefore move only a short distance.
- Take interval scheduling by earliest finishing time and write the exchange argument out in full: given any optimal schedule, swapping in the earliest-finishing interval keeps it feasible and no smaller. Then construct the weighted variant where that same greedy fails and name what has to replace it.
Deliverable: A three-way top-k comparison with measured crossover points, one written exchange argument, and one counterexample to a greedy rule that looks almost identical.
Day 4 — Recursion, memoisation, and the step to a table
- Take one problem with overlapping subproblems, such as edit distance or coin change, instrument the plain recursion with a call counter to show the blow-up, then add memoisation and re-count.
- Convert the memoised version to a bottom-up table and state the two properties you relied on: each subproblem's result depends only on its arguments, and the dependencies form a DAG you can enumerate in order.
- Rewrite one deep recursion with an explicit stack, then find the input length at which the original hits the interpreter's frame limit, which defaults to about a thousand frames in CPython, so you know when the rewrite is required rather than decorative.
Deliverable: One problem in three forms, naive, memoised and tabulated, with call counts for each and the input length at which recursion depth becomes the binding constraint.
Day 5 — Graphs, where most of the work is choosing the traversal
- Implement BFS and DFS over one adjacency list, then answer for each which finds a shortest path in an unweighted graph and which you would use to detect a cycle in a directed graph, including why the in-progress versus finished distinction matters for the second.
- Implement topological sort by in-degree, feed it a graph containing a cycle, and confirm the failure signature is that fewer than V nodes come out rather than an exception, then note that the order it produces is one of several valid ones.
- Run a shortest-path search on a graph with a single negative edge weight and show the wrong answer, then write the precondition Dijkstra actually needs, non-negative weights, because it finalises a node's distance the first time that node is popped, and name the algorithm you would switch to and its own limit.
Deliverable: A small graph library with BFS, DFS and topological sort, plus two inputs that produce documented wrong answers under the wrong algorithm choice.
Day 6 — One day for everything that is not an algorithm
- Sketch one system only to the depth a coding-heavy loop tends to reach: the endpoints, what the service stores, and the single query pattern that decides the schema. Stop at twenty-five minutes.
- Prepare the project answer for an interviewer who codes, which means rehearsing the two levels they push to: the specific thing you built, and why you chose that approach over the alternative they will name. Open with a number and be ready to say what it excludes.
- Prepare the answer to what you would do differently, choosing a real technical mistake with a specific fix rather than a complaint about process or staffing.
Deliverable: One design sketch at endpoint-and-schema depth, plus a project answer rehearsed to two levels of follow-up.
Day 7 — Solve out loud, under time
- Do three timed problems at twenty-five minutes each in a plain editor with no autocomplete and no execution until the end, then tally separately the failures that were syntax and the ones that were approach, because those two numbers call for different fixes.
- Narrate one solution from the first sentence, stating the approach and its complexity before writing any code, and rehearse the sentence you will use when you realise mid-solution that the approach is wrong.
- Re-solve from blank the two problems you were slowest on this week and compare the times against the day they first appeared.
Deliverable: A recording of one fully narrated solution and a tally that separates syntax failures from approach failures.
The failure that was yours
Keep one story where the bad call was yours rather than a dependency's or a manager's. Name the check that would have caught it, whether you added that check afterwards, and whether it has fired since. Answers that route blame outward end the conversation early; answers that end in a guardrail someone still relies on tend to open it up.
Tell us about a time you had to explain a technical concept to a non-technical stakeholder.
Medium · behavioural and engineering judgement
Approach
- Pick a story where you made the decision, not one where you watched it.
- 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?
- What would you do differently if you ran that again?
Describe your experience with full-stack development and how you manage state between the frontend and backend.
Medium · behavioural and engineering judgement
Approach
- 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.
- Pick a story where you made the decision, not one where you watched it.
Follow-up
- What would you do differently if you ran that again?
- How did you know your change caused the improvement?
Describe a challenging team situation and how you contributed to its resolution.
Medium · behavioural and engineering judgement
Approach
- Close with what you would do differently, concretely.
- 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.
Follow-up
- What would you do differently if you ran that again?
- What did you decide not to do, and why?
Why are you interested in the intersection of finance and technology at Bayview Asset Management?
Medium · behavioural and engineering judgement
Approach
- Close with what you would do differently, concretely.
- 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.
Follow-up
- What did you decide not to do, and why?
- What would you do differently if you ran that again?