Interview guides · Aleph Alpha
Aleph Alpha Software Engineer Interview Questions & Guide 2026
Timed coding practice for Aleph Alpha software engineer candidates: full attempts under a clock, then scored against a rubric.
The 60-second brief
As a Software Engineer at Aleph Alpha, you are at the forefront of the generative AI revolution. This role is critical to building the infrastructure, models, and applications that define the company’s mission to provide sovereign, transparent, and reliable AI solutions for enterprise and government sectors. You will work on complex challenges ranging from high-performance computing and distributed systems to application-level development that makes advanced AI accessible.
The shape of the workload matters more for your prep than the industry label does. Read-heavy serving, write-heavy ingestion and scheduled batch processing have different binding constraints and fail in different places, so find out which one the team lives in before picking design topics.
Focus areas
- Fold execution reports idempotently on venue exec id
- Detect sequence gaps before pricing off a book
- Replay recorded input through the same build
Overview
The work is high-stakes and technically demanding. You will contribute to a product ecosystem that prioritizes data security and explainability, often working with state-of-the-art technology stacks. Whether you are optimizing low-level performance or architecting scalable services, your contributions directly impact how organizations interact with and trust large language models. Expect a fast-paced environment where your ability to navigate ambiguity and deliver robust, production-grade code is essential.
The Aleph Alpha interview process
Aleph Alpha candidates report 4 rounds · ≈ 3-5 weeks. The stages below are what candidates describe, not a published process.
Recruiter Screen
The person on this call usually cannot evaluate your code and does not need to. They write a short paragraph, and that paragraph is what a hiring manager skims when deciding who to put on your loop. So the test is not whether your work was hard, it is whether a non-engineer can repeat it correctly. Name systems by what they did rather than by their internal codename, give each project a shape (what was breaking, what you changed, what happened after), and keep the whole walkthrough near ninety seconds. Depth that cannot survive a paraphrase reads as vagueness.
What to demonstrate
- Whether a non-engineer can restate your projects without distorting them, since their paraphrase is what travels to the hiring manager, not your sentences
- Whether each project has a shape rather than a stack list: the failure or constraint, the change you made, the result and how it was measured
- Whether you can say what was yours inside a team project without either inflating it or disappearing into the plural
How to prepare
- Rewrite each headline project as two sentences with no internal system names and no acronyms outside your company, then say them to someone outside engineering and have them repeat them back. Fix whatever came back wrong
- Attach one measured number to each project: the baseline, the change, and the window it was measured over. Where nothing was ever measured, say that plainly rather than reaching for a plausible percentage
- Time the background walkthrough against a clock. If it runs past two minutes, compress the earliest role to a single clause and spend the recovered time on the most recent one
Technical Challenge
The same problem is scored by two different mechanisms depending on the format, and preparing for one does not cover the other. With a person watching, partial progress is visible and a hint is a correction you can absorb; silence is the expensive failure, because nobody can read a half-written function. With an automated grader there is no partial credit for what you were about to do, nobody to ask, and the worked examples in the prompt are the entire specification. Read them as a contract, down to whether an empty result should be an empty list or no output at all.
What to demonstrate
- In a live session, whether your commentary tracks what your hands are doing, and whether a hint redirects you or gets defended against
- In an automated one, whether you cover the cases the examples do not show, since the hidden cases are where the score moves
- Whether you manage the clock on purpose: abandoning an approach that is not converging while there is still time to write something simpler that finishes
How to prepare
- Have someone hand you a problem and feed you one deliberately wrong hint. Practise testing it against a concrete case instead of accepting or rejecting it on authority.
- Do one timed run a week in a plain browser editor with autocomplete, linting and your own snippets switched off, which is closer to what these environments give you
- For the automated format, write the harness before the solution: a main that feeds the worked examples plus an empty and a single-element case and prints expected against actual, so a wrong submission is caught by you first
Technical Deep Dives
Input bounds are the part of the prompt most often skimmed, and they usually contain the answer. They tell you which complexity class is admissible, which narrows the search before you have thought about the problem itself. As a rough planning figure, a compiled language does on the order of 10^8 simple operations per second and an interpreted one roughly an order of magnitude less. So n up to about twenty admits enumerating subsets, a few thousand admits a quadratic pass, and a million admits neither: you need near-linear, or linear with a log factor. If the bounds are missing, ask for them.
What to demonstrate
- Whether the approach is justified by the stated input size rather than by whichever pattern you recognised first
- Whether you ask about the properties that change the algorithm: whether the input arrives sorted, whether duplicates occur, whether values are bounded integers, whether it all fits in memory
- Whether you can name the bottleneck in your own solution and what would remove it, even when you deliberately leave it in place
- Whether a claimed speedup is real, since memoising a recursion only helps when subproblems genuinely overlap and the state can be keyed cheaply
How to prepare
- For each algorithm you rely on, write down the largest n it handles in roughly a second, then check two of those figures by timing them in the language you will actually type in
- For two weeks, write one line naming your target complexity and the bound that justifies it before you write any code, then compare that line with what you ended up submitting
- Practise the conversion backwards: given a required O(n log n), list the mechanisms that get you there (sorting, a heap, an ordered map, divide and conquer) and choose by what the problem needs to query, not by what you used last
Leadership Discussions
You cannot drill a format you do not know, so put the preparation into material that travels. Three pieces of your own work, each rehearsed until you can take a follow-up you did not anticipate, will carry a conversation or a code walkthrough equally well. Specificity is what separates that from filler. A number needs its definition before it means anything: a p99 is over some window and measured at some hop, and a server-side figure excludes the queueing and network time a client would see. The number you cannot qualify is the one to leave out.
What to demonstrate
- Whether your examples carry detail only someone who did the work would hold, such as what the binding constraint actually was, which alternative you rejected and why it was worse, and what you measured on each side of the change
- Whether a number survives one follow-up, meaning you can say what it was measured over and whether it moved because of your change or merely alongside it
- Whether a failure is described with the specific change that followed it, rather than a lesson stated in general terms
- Whether your part in a team effort is stated accurately, including what other people did
How to prepare
- Write a page on each of three projects covering the constraint, the option you rejected, the measurement before and after, and what went wrong. Cut any line you cannot take a follow-up on, since you are writing the parts you will be pressed on rather than a summary.
- Recover the real figures while you still have access: request volume, data size, latency with its percentile and window, team size, timeline. Note where each came from, whether a dashboard, a design document or memory, and mark the estimates so you can say which they are out loud.
- Take your weakest project story to someone who works in a different area and have them ask why four times in succession. The point where you run out of answer is the part to go and re-read before the round.
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.
Optimising an axis nobody named
Ask which resource is actually scarce here: wall-clock latency, throughput, memory footprint, cost per request, or engineering time. Shaving a constant factor off an in-memory step is wasted effort when the same function makes a blocking remote call inside the loop.
Retrying a write that is not safe to repeat
A timeout tells you nothing about whether the server applied the write, so a blind retry of a create or a charge can duplicate it. Either make the operation idempotent, with a caller-supplied key the server deduplicates on or a conditional update, or do not retry it; and use exponential backoff with jitter so the retries of many clients do not synchronise into a second outage.
Questions & practice
Implement a new feature or fix a bug in a real-world codebase.
Medium · data structures and algorithms
Approach
- Walk one small example through your approach before writing the whole thing.
- 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?
Make requested changes to an existing system during a live session.
Medium · data structures and algorithms
Approach
- Choose the data structure from the access pattern, not from familiarity.
- Walk one small example through your approach before writing the whole thing.
- State the target complexity and say which constraint rules the naive version out.
Follow-up
- Which test case would catch an off-by-one here?
- What is the worst case, and how likely is it on real data?
Solve specific issues within a provided GitHub repository.
Medium · data structures and algorithms
Approach
- State the target complexity and say which constraint rules the naive version out.
- 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?
- Which test case would catch an off-by-one here?
Resolve position identity across a corporate action graph
Hard · dag traversal · cycle detection · exact arithmetic
Corporate actions are edges (from_instrument_id, to_instrument_id, action_type in symbol_change, merger, spinoff, expiry_roll, ratio_num, ratio_den, effective_ts) — up to 2 x 10^6 edges over 10^6 instruments. Given a position in instrument I held at t0, return the set of (instrument_id, exact rational multiplier) that it becomes at t1 > t0, following only edges whose effective_ts lies in (t0, t1]. Mergers give a node in-degree above one; spinoffs give out-degree above one. Detect cycles and report them as data errors rather than breaking them. Give both the per-query and the batch complexity.
Approach
- Start from what the shape of the answer has to be. A spinoff makes out-degree greater than one, so a position maps to a set, not to a single successor; a merger makes in-degree greater than one, so the reverse map is not a function. Anything that collapses each instrument to one representative — union-find being the usual reflex — cannot express either, and also destroys the as-of property that makes the query meaningful.
- Filter the edge set to effective_ts in (t0, t1] and traverse forward from I with DFS, carrying an exact rational multiplier along each path as a reduced (num, den) pair. Multiply at each hop, run gcd immediately to keep the components small, and use checked 128-bit multiplication so a long chain fails loudly rather than wrapping.
- Accumulate at the terminals by summing multipliers across distinct paths that reach the same instrument, because a spinoff that later re-merges into its parent genuinely contributes twice. Summing rationals means a common denominator, then a gcd reduction again.
- Run the cycle check before the path-carrying traversal, not after it. The DFS above carries a multiplier down every distinct path and so does not terminate on a cyclic subgraph at all; the traversal's termination is a precondition that Kahn establishes, not a property the traversal has on its own.
- Complexity: a single query is O(V + E) in the worst case. For a batch — end-of-day, every position in the book — sort nodes by effective_ts to get a topological order of the filtered subgraph and memoise the resolution per (node, t1), which makes the whole batch O(V + E) once instead of O(positions x (V + E)).
- Detect cycles with Kahn's algorithm on the filtered subgraph: after the queue drains, the residual set is exactly the nodes on or downstream of a cycle. Run Tarjan on the residual to name the strongly connected components of size above one, report them with the offending edges and exit non-zero. Time is monotone along real corporate actions, so a cycle is always a vendor or load error; deleting an edge to make the traversal terminate hides the error and leaves positions mapped to the wrong instrument.
- Keep ratios rational end to end. A 1/3 hop followed by a 3/1 hop — a consolidation and then a re-split, through distinct instruments — must compose to exactly 1/1, which it does with reduced rationals and does not with binary64, where the round trip lands near 0.9999999999999998 and a position quantity is then off by a share after rounding.
Worked solution (40 min)
- Build the fixture inside the query window (t0, t1]: A -> B symbol_change 1/1 at ta; A -> D spinoff 1/4 at ta; B -> C merger 2/5 at tb, with t0 < ta < tb <= t1.
- Resolve a position of 1000 units of A by hand along both paths, multiplying reduced rationals rather than decimals.
- Run the traversal and compare its (instrument, multiplier) set against the hand computation.
- Extend the fixture with a chain through distinct nodes rather than a round trip: A -> E symbol_change 1/3 at ta and E -> F symbol_change 3/1 at tb, both inside the window, and confirm the multiplier carried to F composes to exactly 1/1. Pointing the second leg back at A instead would close a cycle, which is the next step's fixture and not a test of rational arithmetic.
- Add an edge C -> A at a timestamp inside the window, rerun, and confirm Kahn leaves a residual and Tarjan names the cycle before any path-carrying traversal starts.
- Remove the C -> A edge, delete the spinoff edge, and rerun to confirm the result set shrinks without disturbing C's or F's multiplier.
Expected result: On the three-edge fixture, 1000 units of A resolve to {C: 400, D: 250} — C via 1/1 then 2/5, D via 1/4 — with multipliers held as 2/5 and 1/4 rather than 0.4 and 0.25. Adding the A -> E -> F chain makes the set {C: 400, D: 250, F: 1000}, since 1/3 then 3/1 composes to exactly 1/1. With the C -> A edge present, Kahn's residual is {A, B, C, D, E, F} — the cycle plus everything reachable from it — and Tarjan names the one strongly connected component of size above one, {A, B, C}; the job reports it with the offending edges and exits non-zero rather than looping.
Verify it yourself
- The 1/3 then 3/1 composition down A -> E -> F equals 1/1 exactly after gcd reduction; the same computation in binary64 does not.
- The cycle fixture terminates and names the component, rather than either hanging or silently dropping an edge.
- Removing the spinoff edge drops D from the result and leaves C at 2/5 and F at 1/1 untouched, confirming the paths are accumulated independently.
Follow-up
- A vendor correction arrives at 18:00 changing yesterday's merger ratio. What do you recompute, and what does that do to already-published positions?
- A cash-in-lieu component means the mapping is not purely quantity-to-quantity. Where does that land in the model?
- How do you make this resolution replayable, so a backtest run in six months resolves the same identity the live session did?
Current order state from an append-only event log
Easy · window-functions · indexing · event-sourcing
order_event is append-only: event_id BIGINT PRIMARY KEY, order_id BIGINT, strategy_run_id UUID, seq_no INT (1-based and contiguous per order, UNIQUE (order_id, seq_no)), status_after, cum_qty, leaves_qty, received_ts TIMESTAMPTZ(6). The table holds 10^8 rows across 10^7 orders. Write the query returning current status_after, cum_qty and leaves_qty for every order in a supplied list of order_ids, then the variant returning current state for all orders of one strategy_run_id. State the index each needs, and why ordering by received_ts instead of seq_no is wrong.
Approach
- For the id list use SELECT DISTINCT ON (order_id) ... ORDER BY order_id, seq_no DESC, which takes the first row of each group and stops. The portable equivalent, ROW_NUMBER() OVER (PARTITION BY order_id ORDER BY seq_no DESC) = 1, ranks every row of every group before discarding all but one.
- Index (order_id, seq_no DESC). A btree can be scanned forwards or backwards as a whole, not per key, so the mixed-direction ORDER BY (order_id ASC, seq_no DESC) is not satisfied by (order_id, seq_no) and the planner inserts a sort.
- For the strategy_run_id variant the leading column changes to (strategy_run_id, order_id, seq_no DESC); otherwise the run's orders are found by a scan and grouped afterwards, which is the difference between 10^4 index descents and 10^8 row reads.
- Justify seq_no over received_ts concretely: received_ts is neither unique nor monotonic across reports. Two reports can share a microsecond, and a resend after a reconnect can arrive after a report it precedes, so MAX(received_ts) picks a non-deterministic row and can return an older state than the one the system acted on.
- Name the cost that motivates a maintained order_current table: this query is one index descent per order in scope, fine for a list and unacceptable for a dashboard refreshing every live order in a session. The trade-off is a second write on the order path and a crash window between the append and the update.
Worked solution (20 min)
- Seed one order with six events, giving two of them the same received_ts and inserting the final event with a lower event_id than its predecessor.
- Run the DISTINCT ON query and a MAX(received_ts) variant against that order and diff the two rows.
- EXPLAIN the query under both index shapes, (order_id, seq_no) and (order_id, seq_no DESC), and look for the sort node.
- Repeat for the strategy_run_id variant over a run holding 10^4 orders.
Expected result: One row per order_id carrying the highest-seq_no state. The MAX(received_ts) variant returns a different, older row for the order with the tied timestamp, and the DESC index removes the sort node from the plan.
Verify it yourself
- The result row count equals the number of distinct order_ids requested.
- For every returned row, seq_no equals MAX(seq_no) for that order.
- With the DESC index in place the plan shows no sort node above the index scan.
Follow-up
- How do you assert seq_no is contiguous per order, and what should the system do when it is not?
- Write the query listing orders whose latest row violates cum_qty + leaves_qty = order_qty while the order is live.
- How would you keep order_current correct if the writer can crash between appending the event and updating the summary row?
Check-then-act on a position limit under concurrent submits
Hard · isolation-levels · write-skew · concurrency · pre-trade-risk
A pre-trade check reads the current net position for (account_id, instrument_id) from position_snapshot where snapshot_kind = 'intraday_derived', compares it against risk_limit.limit_value for limit_type = 'max_position_qty', and if the order fits, inserts an order_event row and updates that same snapshot row. Two orders for the same account and instrument are submitted concurrently under READ COMMITTED. Name the anomaly precisely and say what separates it from write skew, say whether PostgreSQL's REPEATABLE READ prevents it and under exactly what precondition, and give three implementations that hold the cap, with the cost of each and what happens when the snapshot row does not yet exist.
Approach
- Pin the interleaving before naming anything. Both transactions read the same position_snapshot row for (account_id, instrument_id): net_qty = 900 against a cap of 1000, both find 900 + 80 inside the cap, both proceed, and both then write that same row. That makes it a read-modify-write conflict on a single row, not write skew. Write skew requires the two transactions to write rows that the other's check did not read, which is precisely not the case here, as Implementation B's conditional UPDATE targeting the very row the check read makes obvious.
- Say which failure the SET clause buys. SET net_qty = 980, the value the application computed from its stale read, is a lost update: the second write overwrites the first, the row settles at 980, and the ledger now disagrees with the 1060 the venue actually filled. SET net_qty = net_qty + 80 loses nothing arithmetically and settles at 1060, over the cap and faithfully recorded. Both admitted two orders against one reading of the headroom.
- READ COMMITTED allows it. The second UPDATE blocks on the row lock, and once the first commits it re-evaluates its WHERE clause against the newly committed row version and proceeds. The write lands against fresh data; nothing re-checks the decision that authorised it.
- REPEATABLE READ does prevent this interleaving, and the precondition is the whole answer. PostgreSQL's REPEATABLE READ is snapshot isolation: a transaction that updates a row which a concurrent transaction committed after its snapshot is aborted with SQLSTATE 40001 rather than let through, so the second submit fails and a replay of the whole transaction re-reads 980 and rejects the order. That holds only because both transactions write the same row. Derive the position from a SUM over inserted order rows instead, so each transaction only appends and neither writes what the other read, and the identical check becomes genuine write skew, which REPEATABLE READ permits and only SERIALIZABLE catches.
- Implementation A, serialize on one row: SELECT ... FOR UPDATE the (account_id, instrument_id, business_date, 'intraday_derived') row inside the transaction. Correct only if every writer takes the same lock, and it has a hole worth naming: FOR UPDATE matching zero rows locks nothing, so the first order of the day in an instrument needs INSERT ... ON CONFLICT to materialize the row, or a lock on a surrogate such as pg_advisory_xact_lock over a hash of the pair, remembering that a hash collision merely over-serializes two unrelated pairs rather than letting either through. Cost: every order in a hot instrument serializes behind one row.
- Implementation B, collapse the check and the act into one statement: UPDATE position_snapshot SET pending_qty = pending_qty + :qty WHERE ... AND pending_qty + :qty <= :limit_value, and treat zero affected rows as the rejection. At READ COMMITTED an UPDATE that blocks on the row lock re-tests its WHERE against the version the other transaction committed, so the predicate is evaluated against fresh data with no window between reading and acting, and the second order is rejected with zero rows rather than admitted. Note the level interaction before deploying it: the same statement at REPEATABLE READ or SERIALIZABLE raises 40001 instead of quietly rejecting, so a retry loop is mandatory there rather than optional.
- Implementation C, SERIALIZABLE: serializable snapshot isolation tracks read-write dependencies rather than row versions, so it covers both shapes, the single-row conflict here that REPEATABLE READ already catches and the append-only variant it does not. It aborts with SQLSTATE 40001 and does not block. The retry has to replay the transaction from BEGIN including the read the decision rested on, because an aborted transaction rejects every further command with 25P02, so retrying just the failed statement is not a thing that exists. Throughput falls as contention on the pair rises, and an unbounded retry loop turns a hot instrument into a livelock.
- Then say what a trading system actually does, because the interviewer is waiting for it: the check is not a database round trip at all. It runs in process on a single-writer order path against a preloaded, versioned limit snapshot, because one round trip costs more than the entire tick-to-trade budget. The rows are the audit record, and the check records which limit_version it evaluated against so a rejection is explainable months later.
Follow-up
- A limit is tightened at 10:00:00.000 while an order approved at 09:59:59.999 is in flight. What does the venue see, and what does your ledger record?
- Your in-process check is the authority. How do you bound its divergence from the database, and what happens when they disagree?
- Two order gateway processes serve the same account. What stops each from believing it owns the whole position?
How do you approach debugging or extending existing CLI tools?
Medium
Approach
- Clarify what is being asked and what a complete answer contains.
- State your assumptions explicitly before working the problem.
- Say what you would check first and why it is the highest-information step.
Follow-up
- How would you know your answer was wrong?
- What assumption would you test first?
Can you explain how you would manage Kubernetes, Helm, and networking configurations in a production environment?
Medium
Approach
- Work from the requirement backwards to the design.
- State your assumptions explicitly before working the problem.
- Say what you would check first and why it is the highest-information step.
Follow-up
- How would you know your answer was wrong?
- What assumption would you test first?
Keyset pagination over an append-only execution report feed
Easy · pagination · keyset · append-only
A back-office reconciliation job pulls the day's fills in pages of 1000 and must miss nothing. execution_report is append-only with PRIMARY KEY (venue_mic, venue_exec_id) and columns order_id, exec_type, last_qty, cum_qty, venue_ts TIMESTAMPTZ(6) and received_ts TIMESTAMPTZ(6). Design GET /v1/executions with a cursor: define the sort key, the cursor encoding, the index it requires, and the page contract. Explain why OFFSET is wrong here, and what the job must do about rows that commit after its cursor has already passed their received_ts.
Approach
- Sort on a total order, not a timestamp alone: ORDER BY (received_ts, venue_mic, venue_exec_id) with a matching composite btree index, and page with a row-value predicate WHERE (received_ts, venue_mic, venue_exec_id) > (:ts, :mic, :exec). Ties on received_ts are inevitable at microsecond resolution under concurrent writers, and a non-total sort silently skips or repeats rows at page boundaries.
- State the cost difference: the keyset predicate is one index descent plus a range scan, O(log n + k) per page, while LIMIT ... OFFSET n re-walks and discards n rows, O(offset + k). A 10 million row day in 1000-row pages is 10,000 pages, so the last page walks about 10^7 rows against the first page's 10^3, roughly ten thousand times the work, and the per-page cost climbs linearly with page number instead of staying flat.
- Encode the cursor as an opaque base64 token over the tuple so the sort key is not a public contract and can be extended later without a version bump; validate it server-side and reject a token that does not parse rather than falling back to offset zero.
- Name the real correctness hazard: received_ts is assigned by the writer before commit, and rows become visible only at commit, so a slow transaction can make a row visible whose timestamp is below a watermark the reader already passed. Nothing errors; the row is simply never returned.
- Fix it one of two ways: order by a commit-ordered monotonic sequence assigned at insert by a single allocator, or hold the reader a safety lag behind now() that exceeds the longest write transaction and re-read a trailing overlap window. The PRIMARY KEY (venue_mic, venue_exec_id) makes re-reading cheap, because the sink deduplicates.
- Define the page contract: server-capped limit, an items array, and next_cursor returned even on a short page so a tailing consumer can keep polling without inventing a termination rule.
Worked solution (25 min)
- Write the SQL for page one and page N with the row-value comparison, and the CREATE INDEX that serves it.
- Write the same query with OFFSET and state the row count each version touches for page 1 and for page 10000, the last page of a 10 million row day.
- Draw a timeline with two overlapping transactions, T1 starting first and committing second, and mark the point where a timestamp-watermark reader loses T1's row.
- Choose commit-ordered sequence or safety lag, and write the resulting reader loop including the overlap window.
- Write the page response JSON with items, next_cursor and the server-enforced limit.
Expected result: A cursor over the total order (received_ts, venue_mic, venue_exec_id), opaque on the wire, served by one composite index at O(log n + k) per page, with an explicitly stated mechanism (commit sequence or bounded lag plus overlap re-read) that closes the commit-ordering gap, relying on the primary key for deduplication.
Verify it yourself
- Insert two rows with identical received_ts at a page boundary and confirm both are returned exactly once.
- Hold a write transaction open across a reader's full pass, commit it, and confirm the row is still delivered.
- Compare query plans for page 1 and page 10000 and confirm the same number of rows is read.
Follow-up
- The job wants to filter by account_id. What does that do to your index and to the cursor, and does the filtered sort key remain total?
- How large must the safety lag be, and how would you measure the number rather than guess it?
Replay diverges once in twenty runs on wide machines
Hard · determinism · concurrency · floating point
Replaying one recorded session through one build produces outbound order streams that differ in about one run in twenty. It reproduces only on 32-core hosts, never under the thread sanitizer, and never in the single-threaded debug build. Two divergence shapes appear: two orders swapping position in the stream, and one limit price differing in the last digit. Give an ordered checklist that locates the first diverging decision and separates the two causes, naming what you would pin and in what order.
Approach
- Make the divergence cheap to locate. Byte-diffing two output streams finds the first differing byte, which is usually far downstream of the first differing decision. Hash each decision into a running chain keyed by the input event's sequence number; the first differing chain entry names the event and the decision, turning a rerun-until-it-happens loop into a single comparison.
- Separate the two shapes, because their cause sets are disjoint. An ordering difference is an interleaving or an iteration-order problem. A value difference in the last digit is arithmetic, and the precise statement matters: one binary executing the same operations in the same order on the same inputs is bit-reproducible, so a value differing across runs means the order of operations changed or a different code path executed.
- For the ordering shape, enumerate and pin one at a time: more than one thread consuming the input stream; a work-stealing pool whose chunk boundaries vary with scheduling; a container whose iteration order is not a function of insertion order - a map keyed on a pointer whose value moves with address layout, a runtime with randomised map iteration or a per-process hash seed, or a set of objects with no stable hash. Force single-consumer ingestion inside the strategy boundary and replace those containers with ordered ones keyed on instrument_id or sequence number.
- For the value shape, pin the arithmetic. A parallel reduction whose partition depends on thread count or on stealing gives a different summation order and therefore different rounding; runtime dispatch to a different vector kernel does the same. Then pin the compiler: fast-math permits reassociation, and contracting a multiply and an add into a single fused operation changes the rounding of the intermediate. Build with contraction disabled and fast-math removed, and make any reduction's partition a function of input length alone.
- Do not treat a quiet sanitizer as a result. It perturbs timing and allocation enough to close the window and instruments only what it can see, so its silence is weak evidence. Run it, but prove the fix by making the run deterministic by construction and then replaying the recording several hundred times with a zero-tolerance comparison.
- Keep it from returning. Add a CI job that replays a fixed recording N times and fails on any difference, and enforce that nothing inside the strategy boundary reads a wall clock: event time arrives as data, and the wall clock is consulted once at ingestion to stamp received_ts.
Follow-up
- It will not reproduce on an 8-core host. Why would core count matter, and does that narrow which of the two shapes you are chasing?
- A colleague proposes comparing prices with a 1e-9 tolerance so the test goes green. What does that cost you six months from now?
- The strategy needs a 200 ms timeout. How do you express it without reading a wall clock?
Suggested practice checklist
For 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.
'We rewrote it in a better framework' is not a story
A migration is a cost you chose to pay, not an achievement. The story is what the old system made expensive, what you measured before committing, what kept serving traffic during the cutover, and what you would have done if the numbers had come back flat. Without those, a rewrite reads as taste.
What are your experiences with CI/CD pipelines and maintaining system reliability?
Medium · behavioural and engineering judgement
Approach
- Name the disagreement and how you resolved it with evidence.
- Pick a story where you made the decision, not one where you watched it.
- Close with what you would do differently, concretely.
Follow-up
- What would you do differently if you ran that again?
- How did you know your change caused the improvement?
Describe a time when you missed a deadline.
Medium · behavioural and engineering judgement
Approach
- State the situation in two sentences and spend the rest on the reasoning.
- Give the blast radius: what could have broken, and what you measured.
- Pick a story where you made the decision, not one where you watched it.
Follow-up
- How did you know your change caused the improvement?
- What would you do differently if you ran that again?
How do you manually identify bank conflicts in code without using a profiler?
Medium · behavioural and engineering judgement
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.
- Name the disagreement and how you resolved it with evidence.
Follow-up
- What did you decide not to do, and why?
- How did you know your change caused the improvement?