Interview guides · Brex
Brex Software Engineer Interview Guide
Brex Software Engineer interview guide: reported coding, debugging, payments system design and values role-play questions, with a 7-day prep plan.
Interviewing at Brex
- Practice bank: 8+ questions
- Rounds: 2
- Typical prep: 1–2 weeks
- Interview reports: 5
Interview rounds
| Round | Questions |
|---|---|
| Technical Screen | 7 |
| Onsite | 1 |
How hard is the Brex Software Engineer interview?
| Difficulty | Share | Questions |
|---|---|---|
| Easy | 13% | 1 |
| Medium | 75% | 6 |
| Hard | 13% | 1 |
What Brex actually tests for
| Topic | Share | Questions |
|---|---|---|
| System Design | 38% | 3 |
| Coding & Algorithms | 25% | 2 |
| Software Engineering Fundamentals | 25% | 2 |
| Behavioral & Leadership | 13% | 1 |
The 60-second brief
Brex combines corporate cards, business banking, global wire payments and travel and expense management into one AI-powered financial platform for businesses. Software Engineers work on the services and interfaces behind those products: payment pipelines, transaction authorization, ledgers, workflow automation and the web front end. Interview prompts tend to use the same domain, so expect money, transactions and spend data in the problems.
Brex candidates report 3 rounds · ≈ 3-5 weeks. The stages below are what candidates describe, not a published process.
What a Software Engineer does at Brex
For preparation, the domain matters most. The reported questions are about money moving and spend data. In coding, you check whether a player can afford a card from gem balances, aggregate spend from a REST endpoint, and flag suspicious transactions by shared attributes. In design, you authorize card transactions and build ledgers for peer-to-peer transfers and gift cards. The SQL bank asks for transaction counts, monthly totals and consecutive-day patterns. Practise these practical domain problems before any abstract algorithm drills.
The sources also describe two formats worth practising on their own: a debugging exercise, where you get an existing codebase with failing tests and fix it, and a values interview that can include live role-play. Prepare for each separately instead of treating them as ordinary coding and behavioural rounds.
The Brex interview process
Brex candidates report 3 rounds · ≈ 3-5 weeks. The stages below are what candidates describe, not a published process.
Recruiter Call
Candidates describe a first conversation with a technical recruiter about background, expectations and role fit. Use it to learn what comes next. Ask whether your technical screen is a live call or an online practical assessment, which modules the virtual onsite includes (candidates report practical coding, debugging, system design and a values interview), and what environment the debugging exercise expects. Describe your scope in concrete terms: the systems you owned end to end and any payments, ledger or transaction work, because that is the material the later rounds draw on.
What it evaluates
- Whether your background and expectations fit the role as the recruiter understands it
- Whether you can explain your most relevant work clearly and briefly, especially services that handle transactions or money
How to prepare
- Ask whether the technical screen is live or an online assessment, and which languages and tools it allows
- Ask which onsite modules you will have and whether the debugging exercise runs in your own local environment
- Prepare a short account of two systems you owned end to end, including the failure you were responsible for fixing
- Settle your compensation expectation and its split before the call so you can give a number when asked
Technical Screening
Candidates report this as either a live technical call or an online practical coding assessment focused on data structures, domain modeling or API integration. The reported coding prompts match that description. One is a card-and-gem purchase engine that grows through can_purchase, purchase and colour discounts. Another fetches transactions from a REST endpoint and computes monthly totals, peak months or merchant streaks. A third matches flagged transactions by shared attributes. The sources do not say which of these appear in the screen itself, so practise all of them in both formats. For a live call, talk through the code as you write it. For an automated assessment, test against the given examples and the cases they leave out.
What it evaluates
- Whether your domain model survives an extension, such as adding purchase and discounts to an affordability check, without a rewrite
- Whether you handle real API data correctly: pagination, nested JSON, ISO-8601 timestamps and time zones
- Whether the code runs and has been checked against edge cases such as zero balances, missing fields or an empty response
How to prepare
- Build the gem engine in three passes (affordability, then purchase with state updates, then per-colour discounts and wild gems), and reuse one effective-cost function throughout
- Write a script against any public paginated JSON API that follows every page and computes a monthly total and a longest consecutive-day streak
- For the automated format, write a small harness first that runs the given examples plus empty and single-record inputs, and prints expected against actual output
Virtual Onsite Loop
The source describes the virtual onsite as several focused modules run by senior engineering team members. Candidates can take it in one day or split it across two consecutive days. The reported evaluation areas are practical live coding and API integration, debugging an existing codebase against failing tests, distributed systems design for financial workloads, and a values interview that can include role-play. Each one needs a different approach. In coding, finish and test. In debugging, read before you edit. In design, start from requirements and failure modes. In the role-play, hold a live conversation instead of reciting a story.
What it evaluates
- Debugging: whether you read failing assertions and stack traces before changing code, and fix root causes without breaking existing API contracts
- Design: whether you bring up idempotency, double-entry ledgers, locking and double-spend prevention in flows such as card authorization or peer-to-peer transfers
- Values: whether you communicate directly, show ownership and negotiate a trade-off with the people in a role-play
- Coding: whether you finish working, tested code for practical prompts such as API consumption or domain state logic
How to prepare
- Build a small multi-file repo with planted bugs (off-by-one day-of-week index, missing leap-year rule, wrong holiday-override precedence) and practise fixing it from the failing tests alone
- Install and verify your language runtime, SDK and test runner on the machine you will use before the day
- Design the card authorization engine and a peer-to-peer transfer service end to end, and name your idempotency, ledger and locking choices before anyone asks
- Run a role-play mock where a partner plays a product manager pushing a feature date against your technical-debt fix
Questions Brex candidates report
Data structures and algorithms
State the bound, then read your own code against it, because the cost you forgot is usually inside a call you did not write. A membership test against a list is linear while the same test against a set is expected constant, so one of those inside a loop turns a linear scan quadratic. Taking a slice copies the elements wherever slices are values rather than views. Repeated string concatenation is quadratic wherever strings are immutable, unless you accumulate into a builder or a list and join once. None of that is visible in the shape of the algorithm, and all of it is visible in the runtime.
- Card Game & Gem Engine: Implement a
can_purchasefunction to determine if a player can afford a card given their current gem balances (e.g., red, green, blue). Extend this with apurchasefunction that updates state, and introduce a discount mechanism where holding cards of a specific color reduces future purchase costs for that color. - Mathematical String Evaluation: Implement a function that parses and evaluates string-based mathematical expressions containing operations and parenthesis (e.g., evaluating
"5 times (20 plus 30)"). - Attribute Matching & Similarity: Given a list of flagged transaction entries with multiple metadata attributes (such as user, location, and merchant), compare candidate entries and flag suspicious activity based on attribute matching thresholds ($k$ common attributes).
System design and architecture
A design is a set of promises about what happens when something is slow, down, or delivered twice, and that is the part usually left unsaid. For each dependency, say what the system does when it is unavailable: serve stale data, reject the write, or accept it and settle later. For each relaxation of consistency, describe the anomaly a user would actually notice, in the product's own words, rather than naming a consistency model and moving on. The separator is volunteering the failure behaviour as part of the design instead of producing it only when pushed.
- Gift Card & Rewards Ledger: Design a scalable architecture for issuing, redeeming, and auditing gift cards and corporate rewards points across global enterprise clients.
- Credit Card Authorization Engine: Design an end-to-end transaction processing system that receives webhooks from card networks (e.g., Mastercard), verifies account balances, applies spending rules/limits, and responds with an approval or decline in under 100 milliseconds.
- Task Orchestration Infrastructure: Design an asynchronous workflow engine that triggers dynamic multi-channel notifications, approval routing, and third-party SaaS integrations based on enterprise spend events.
Debugging and production issues
Every claim in this kind of answer should be traceable to a signal you could actually query, and you should know what that signal cannot show. Means hide tails, scrape intervals hide bursts shorter than the interval, and uniformly sampled traces mostly miss rare failures. The weak answer reasons about what the system probably does; the strong one names the dashboard, the query and the shape it expects, then says what it would add when the signal does not exist. Saying you have no visibility into something and describing what you would instrument beats a confident guess.
- Backend Service Test Suite: Given a small service with multiple failing unit and integration tests, debug latent concurrency or state mutation bugs to restore full test suite passing status without breaking existing API contracts.
- Holiday Calendar & Date Offset Debugger: Given an existing multi-file codebase designed to compute delivery schedules based on official calendar holidays, identify and fix subtle bugs related to day-of-week indexing, leap years, and holiday overrides.
Behavioural and engineering judgement
Every question in this category has a number in it somewhere, and engineers routinely tell the story without one. What the outage cost, what the delay cost, what the rewrite consumed in engineer-weeks against what it saved, and who absorbed it when it was not users: the on-call rotation, another team's quarter, the support queue. You will rarely have an exact figure, and an estimate with its method stated beats both a precise-sounding invention and silence. Say how you got to it and what it leaves out. That is the sentence that makes the follow-up about whether it was worth doing answerable at all.
- Handling Constructive Feedback: Describe a project where you received critical feedback on your architecture or implementation, how you processed it, and what changed in your execution.
Practice exercises with worked solutions
The prompts above tell you what gets asked.
Make a charge endpoint safe under concurrent duplicate retries
idempotency_key holds id, scope, key, request_fingerprint (SHA-256 over the canonicalised body), status (in_progress, completed, failed), response_status, response_body, locked_at, completed_at, expires_at, created_at. Fifty identical create-payment requests carrying the same scope and key reach four application instances inside the same 20 ms. Give the DDL constraint and the exact statements the handler runs so that exactly one payment_intent is created and all fifty callers receive the same response body. State what you return when that key arrives with a different fingerprint, and what an arrival after expires_at means.
How a strong candidate works it
- Put the concurrency control in the schema: UNIQUE (scope, key). A SELECT-then-INSERT cannot work because both transactions can read nothing before either commits, so the check passes twice and the constraint then surfaces as an error on a payment that succeeded.
- Claim the key with INSERT ... ON CONFLICT (scope, key) DO NOTHING RETURNING id. A conflict returns zero rows rather than the existing row, so branch on rowcount: the winner proceeds, the loser reads the stored row.
- Keep that path on READ COMMITTED deliberately. The loser's follow-up SELECT takes a fresh statement snapshot and therefore sees the winner's committed row; under REPEATABLE READ the transaction snapshot predates that commit, the row stays invisible and the loser concludes the key does not exist.
- Split the work across two transactions because the processor call cannot sit inside one: commit the in_progress row with locked_at first so losers can see a claim, perform the effect, then write payment_intent plus status=completed with response_status and response_body in a single second transaction.
- Handle the crash window explicitly: a row stuck in_progress past its lease is an unknown outcome, not a failure, so the reaper queries the processor for that key before deciding. A loser that sees in_progress returns 409 and retries rather than repeating the effect.
- Compare request_fingerprint before replaying anything. Same key with a different body is 409, never the cached response, because replaying confirms a payment the caller did not request; and set expires_at beyond the client's and the processor's maximum retry horizon, since a replay after it is a genuinely new request.
Follow-ups
- The handler dies after the processor call and before the local commit. What does the next retry with that key observe, and how does the system converge on exactly one charge?
- Does the downstream processor honour an idempotency key of its own? Who mints it, and what breaks if a fresh one is generated per attempt?
- How do you purge rows past expires_at without the delete contending with the insert path?
Version a loan schedule instead of soft-deleting posted instalments
loan_instalment is keyed by (loan_id, schedule_version, instalment_no) and carries due_date, principal_minor, interest_minor, fee_minor, paid_principal_minor, paid_interest_minor, status, days_past_due, effective_from (date) and superseded_at (timestamptz). A borrower defers two payments on 2026-03-14, and instalments 1 to 6 already have allocations posted against them. Model exactly what the deferral writes, and write the query that returns the schedule as the borrower saw it on an arbitrary date. Say why stamping the old rows with deleted_at, or updating them in place, fails an audit.
How a strong candidate works it
- Treat the deferral as an insert, not an edit: write a complete new schedule_version with effective_from = the deferral instant on 2026-03-14, and in the same transaction stamp superseded_at on every row of the outgoing version with that identical instant, so the two version windows are half-open and adjacent rather than overlapping. Instalments 1 to 6 are reproduced unchanged, because they are what the borrower was told and what was posted to the ledger.
- Write the as-of read as a version selection, not a row filter: WHERE loan_id = $1 AND effective_from <= $2 AND (superseded_at IS NULL OR superseded_at > $2), then assert that exactly one schedule_version comes back - COUNT(DISTINCT schedule_version) = 1, not one row - so an overlapping window raises rather than silently returning two interleaved schedules under one instalment_no.
- Fix the type mismatch before writing that predicate: effective_from is declared date and superseded_at timestamptz, so comparing them casts the date at the session TimeZone and two readers in different zones select different versions near midnight. Put both columns in one domain, timestamptz, keep the window half-open with effective_from inclusive and superseded_at exclusive, and resolve a bare as-of date to an instant once, in the loan's booking timezone, at the edge of the system.
- Say what deleted_at loses. It records that a row stopped being current but not what replaced it or from when, it leaves every downstream query obliged to remember deleted_at IS NULL, and one query that forgets double-counts the schedule. Versioning puts the same information in the primary key where it cannot be forgotten.
- Close the arithmetic: under the loan's stated day-count convention the new version must still sum to outstanding principal plus scheduled interest to the minor unit, with the per-period rounding residual placed in one named instalment, conventionally the last, rather than smeared across the tail.
- Index (loan_id, effective_from DESC) for the as-of lookup, keep superseded versions online rather than archiving them, and enforce with a trigger that no UPDATE touches a row whose paid_principal_minor or paid_interest_minor is non-zero.
Expected result (25 min): Two schedule_versions for the loan. The as-of query returns version 1 for 2026-03-01 and version 2 for 2026-03-20, instalments 1 to 6 are identical across both versions, and SUM(principal_minor) equals the original principal exactly in each version.
Check yourself
- The live-version invariant is per version, not per row: SELECT COUNT(DISTINCT schedule_version) FROM loan_instalment WHERE loan_id = $1 AND superseded_at IS NULL returns 1, while COUNT(*) over the same predicate returns that version's instalment count - 12 here - so an assertion of one NULL row per loan would fail on the correct design.
- Run the as-of query for 200 random dates spanning the loan and assert it returns exactly one schedule_version every time.
- Run the as-of query at exactly the deferral instant and assert version 2 alone: the windows are half-open and version 1's superseded_at equals version 2's effective_from, so the boundary belongs to exactly one version.
- Attempt an UPDATE on an instalment with paid_principal_minor > 0 and confirm the trigger rejects it.
- Change the session TimeZone and re-run the boundary instant; with both columns timestamptz the selected version must not change, which is the test that the date column was actually retyped.
Follow-ups
- What does days_past_due mean for an instalment that exists in two versions with different due_dates?
- A payment arrives allocated to an instalment_no that exists only in the superseded version. What do you do with it?
- How do you prove the ledger postings made under version 1 still reconcile once version 2 exists?
Answer as-of balance queries over an append-only entry log
Given 400 million ledger_entry rows (entry_id, account_id, direction, amount_minor, currency, business_date) and 2 million queries of (account_id, currency, as_of_date) asking for the balance at the end of that business date, produce every answer. The obvious solution — per query, sum that account's entries with business_date <= as_of_date — is correct. Say precisely why it will not finish, then give one that will, with time and space complexity. Corrections are posted as new entries carrying their own business_date.
How a strong candidate works it
- Cost the naive version in numbers before rejecting it. Spread uniformly over 20 million accounts, each query touches about 20 rows behind a per-account index and 2 million queries is 4e7 row touches — perfectly fine. The problem is skew: one pooled clearing or merchant settlement account holding 3e7 entries, taking 10% of the queries, is 6e12 row touches. Name the skew; 'n is large' is not the reason.
- The structural fact that buys a cheap answer: entries are append-only and never updated, so a prefix sum over an account's entries ordered by
(business_date, entry_id)is stable — nothing behind position i can change. No mutable-balance design offers that, and it is why the storage is worth paying for. - Offline sweep, when all queries are known up front: externally sort entries by
(account_id, currency, business_date, entry_id)and queries by(account_id, currency, as_of_date), then merge-walk both with a running sum, emitting each query's answer as the sweep passes its date. O((n + q) log(n + q)) dominated by the sort, O(1) beyond sort buffers, one sequential pass over each input instead of 2 million random seeks. - Online alternative: materialise end-of-day snapshots — one row per
(account_id, currency, business_date)that had activity, holding the cumulative total. A query becomes one index seek for the latest snapshot at or beforeas_of_date, O(log n) per query, over far fewer rows than n. Use snapshots when queries arrive singly and the sweep when they arrive as a batch. - Corrections are the subtlety: an entry posted today but dated back changes historical answers, so every snapshot for that account from that date forward is stale. Either keep a Fenwick tree over dates per account (O(log D) update and prefix query) or recompute that account's snapshots from the corrected date onward. Then be precise about what reproducibility means — yesterday's statement is reproducible as of a stated snapshot time, not identical forever.
- Bound the resources: int64 sums throughout, no float; 400 million rows at roughly 48 bytes of the columns you actually need is about 19 GB, so the sort is external and its fan-out is chosen from the sort buffer, not from the row count.
Expected result (40 min): Both implementations agree on all 50,000 fixture queries. After the back-dated correction, exactly the queries at or after its business_date change, each by the identical signed amount. The sweep touches every entry row once; the naive version touches the hot account's rows once per query against it.
Check yourself
- The balance at the maximum
as_of_dateequals the plain full-file aggregate for that account and currency. - Summed across all accounts at any single date, the result is 0 per currency — provided every entry in a transaction shares one
business_date, which is worth asserting separately rather than assuming. - The sweep's answers do not change when entries are fed in a different physical order, because the sort key is total.
- No answer depends on
posted_at: swapping two entries'posted_atwithin one business date changes nothing.
Follow-ups
- One account holds 30% of all entries. What does the external sort do with it, and what would you do for that one key instead?
- Queries now arrive online at 500 per second. Which design survives, and what does keeping the other one warm cost?
- A correction lands with a
business_date90 days back. Which snapshots are now wrong, and how does a reader find out?
Fan out ordered events to fifty thousand merchant endpoints
Deliver signed events to 50,000 merchant endpoints, at least once, preserving order per destination, retrying with backoff out to 24 hours. Peak is 20,000 events/s with a long tail: most destinations take under one event per minute, a few take thousands per second, and at any moment some are dead or answering in 30 seconds. Design the delivery layer: the partition key, how order is preserved, the concurrency bound, and how one 30-second destination is stopped from delaying the other 49,999. State where backpressure is applied.
How a strong candidate works it
- The ordering guarantee is per destination, so the partition key is destination_id, not event id or aggregate id. That much is forced. What it does not solve is the tail: hash destinations onto a fixed pool and a single 30-second destination adds up to 30 seconds of latency to every destination sharing its partition, which is head-of-line blocking wearing a partitioning key as a disguise.
- Separate scheduling from partitioning: keep a per-destination queue plus a next_attempt_at row, and have workers claim a destination under a lease rather than pull from a shared event queue. Order comes from allowing at most one in-flight request per destination; parallelism comes from the number of distinct destinations claimed at once. Worker count stays bounded and independent of the 50,000 destinations, because an idle destination consumes a row, not a thread.
- Handle the dead ones with a per-destination circuit breaker: after N consecutive failures or timeouts, move that destination to a slow lane with a long poll interval and a small worker share, keeping its events queued and ordered; disable and notify after the 24-hour schedule expires. The retry timer holds the delay, not a blocked worker.
- Accept that a single destination at thousands per second cannot be both strictly ordered and parallel. Either weaken the guarantee to per-aggregate order and partition within that destination by aggregate_id (consumers already order on aggregate_version, so this costs them nothing), or batch several events per request in order and keep one request in flight.
- Apply backpressure at enqueue, per destination: bound queue depth, and past the bound either coalesce event types where only the latest state matters or shed under a documented policy. The outcome to prevent is one destination's 24-hour backlog consuming the storage and IO that the other 49,999 need.
Expected result (30 min): Under per-destination leases, A and C deliver at their own rate and their p99 is unchanged by B. Under a hashed pool of W workers, B contributes up to 30 seconds of latency to every destination sharing a worker, and the pool stalls entirely once the number of stalled destinations approaches W. B's queued events stay in order and deliver on recovery.
Check yourself
- The receiving side observes aggregate_version strictly increasing per destination across the whole run.
- At most one request is in flight per destination at any instant; two means the network chooses the order.
- Worker count is bounded and unrelated to destination count, so 50,000 destinations do not imply 50,000 threads or connections.
Follow-ups
- A merchant returns 200 but never processed the event. Whose problem is it, and what do you offer them?
- A merchant asks to replay three days of events. What in this design makes that cheap or expensive?
- How do you rotate the signing secret without a delivery gap?
Own the postmortem for a duplicate-capture incident
A processor slowed down, callers timed out and retried without reusing their idempotency key, and 412 captures were duplicated over 90 minutes before a reconciliation break report surfaced it. Take the on-call role. Describe an incident you owned of comparable blast radius: how it was detected, how you bounded the affected population, what you stopped first, and how customers were made whole. Give a wall-clock timeline, the query or metric that sized the damage, and the change that would have prevented it. Include what you got wrong during the response, not only after it.
How a strong candidate works it
- The probe is whether you can bound an unknown blast radius under time pressure. Open with the invariant that broke (at most one capture per authorisation attempt) rather than the symptom, because the invariant tells the listener what to count.
- Size the population with a stated query, not an adjective: duplicate captures are ledger_entry rows with source_type='capture' grouped by source_id having count(*) > 1, joined back to payment_intent for the affected merchants and amounts. Say how long that query took and whether you could run it against a replica while the incident was live.
- Separate mitigation from fix and say which you did first. Mitigation is usually cheap and blunt (disable the retry path, drop the caller's concurrency, hold captures behind a flag); the fix is a UNIQUE constraint plus a stored response, and it is not an incident-window change.
- State the remediation arithmetic explicitly: refunds are new customer-visible movements with their own fees and their own settlement lag, so the count of duplicates, the total minor units, the refund posting date and the customer notification are four separate numbers a strong answer has ready.
- Close on the prevention change and its cost. Naming one guard that would have caught it earlier (a break-age alert, a duplicate-capture counter on the ledger write path) beats listing five that nobody staffed.
- Name your own error inside the response window: a mitigation you tried that made it worse, or the 20 minutes you spent on the wrong hypothesis. Interviewers weight that heavily because it is the part candidates rehearse away.
Follow-ups
- The retry came from a client you do not control. What do you change so a client that regenerates its key per attempt cannot cause this again?
- How would you have detected it in 5 minutes instead of 90, and what would that detector cost in false pages per week?
- A merchant disputes your count of affected transactions. What do you show them?
Estimate a reconciliation rebuild you have never attempted
You are asked how long it takes to replace a reconciliation service matching 30 million settlement lines a day against the ledger, including a bounded fuzzy fallback for netted fees and an ageing model for breaks. You have never built one. Produce an estimate, the range around it, and the two or three unknowns that dominate that range. Then describe a time you estimated unfamiliar work: what you did in the first day to shrink the range, what you committed to publicly, how far off you were, and what you would tell the requester differently now.
How a strong candidate works it
- The probe is whether you can be useful under uncertainty without either refusing to estimate or inventing false precision. Give a number with an explicit range and the basis for both, then immediately name what would move it, rather than asking for two weeks of discovery first.
- Decompose into parts with different uncertainty profiles. The hash join on (external_reference, amount_minor, currency, business_date) over 30 million lines is well understood engineering and estimates tightly; the fuzzy fallback for netted and fee-adjusted lines does not, because its scope is defined by whatever the files actually contain; the ageing and break workflow is mostly operations-facing surface area, which estimates by counting screens and states.
- Name the dominating unknowns concretely: how many distinct file formats and cutoff conventions the sources use, what fraction of lines are netted rather than itemised, and whether business_date is derivable from any field in the file or must be reconstructed from the cutoff rule. Each is a factor on the fuzzy path, not a percentage on the whole.
- Describe the first-day range-shrinking work, which is the part that separates strong from generic: take one real file, count distinct formats, measure the netted fraction, and attempt the exact join on a single day of postings to see what the residual actually is. One day of that typically converts a 3x range into something near 1.5x.
- Commit in a form that survives being wrong: a range plus a checkpoint date at which you will replace it with a narrower one, and an explicit statement of what you will cut first if the range turns out to be optimistic.
- In the retrospective half, give the real numbers: the estimate, the actual, and the specific thing that consumed the difference. Answers that were within 10 percent are less informative than answers that were 2x off for a nameable reason.
Follow-ups
- The requester wants one number, not a range, for a board deadline. What do you give them?
- Your one-day probe finds 40 percent netted lines instead of the 5 percent you assumed. What changes in the plan, not just the estimate?
- What do you cut first if you are at the deadline and the fuzzy fallback is not done?
Where candidates lose points
Editing code in the debugging exercise before reading the failing tests
Candidates describe a repository with failing unit and integration tests. The reported bugs are in places like day-of-week indexing, leap-year rules, holiday overrides and state mutation. Run the suite first, read each failing assertion and stack trace from top to bottom, and state the root cause before you touch any code. Fix the smallest thing that explains the failure and re-run everything after each fix. Do not change function signatures or response shapes, because the reported task is to restore passing tests without breaking existing API contracts. Install and verify your runtime, SDK and test runner beforehand so setup does not eat into the exercise.
Writing can_purchase in a way that forces a rewrite when purchase and discounts are added
The reported card-and-gem prompt grows in stages: first affordability, then a purchase that updates state, then discounts for each card colour you hold. Bank variants add wild or gold gems that can cover a shortfall. Keep player state explicit, with gem counts by colour and held cards by colour. Compute the discounted cost per colour in one function that both can_purchase and purchase call. In purchase, validate everything before changing anything, so a failed purchase leaves state untouched. Clamp discounted costs at zero, and spend coloured gems before wild gems. Test these cases: an exact balance, one gem short but covered by a wild gem, and a discount larger than the cost.
Getting timestamps and date windows wrong in spend aggregations
Reported prompts ask for total monthly spend, peak months and consecutive-day merchant streaks from a REST endpoint, and the SQL bank asks for similar aggregations. Follow pagination until the endpoint says there are no more pages. Parse ISO-8601 timestamps into timezone-aware values and say which zone defines a month or a day. Handle missing or malformed records instead of crashing. For streaks, reduce the data to one row per merchant per day before looking for consecutive dates, so two purchases on the same day do not count as a two-day streak. Check month and year boundaries such as January 31 to February 1, and keep amounts as integer minor units in every sum.
Designing a payment flow without idempotency or a ledger
Reported design prompts include a card authorization engine that answers card-network webhooks within a tight latency bound, a peer-to-peer transfer service, a gift card and rewards ledger, and a workflow engine driven by spend events. Bring up correctness without waiting to be asked. Cover an idempotency key per authorization or transfer, enforced by a unique constraint. Use double-entry ledger entries that sum to zero, and row locks or conditional updates on balances. Decide, and say, what authorization returns when a dependency is slow. A timeout is an unknown outcome, so reconcile with the other side before you retry. Store amounts as integer minor units together with the currency code.
Telling a story in the values role-play instead of negotiating inside it
The source describes role-play scenarios in the values interview. One is a priority conflict between delivery timelines and technical debt, played out with two interviewers. Stay in the scene. Ask what each side needs, restate their concern, and put a concrete risk on the debt. Then offer a specific split, such as shipping a guarded scope now and scheduling the fix with an owner and a date. Finish by saying what you would communicate and to whom. Rehearse aloud with a partner playing the other side, because a prepared STAR answer does not carry over to a live exchange.
Design first, for a senior candidate
For a candidate senior enough that the loop turns on design and judgement rather than on whether the coding round gets finished. Five days build one system properly and then stress it; coding gets a single maintenance day, on the assumption that the risk at this level is an unexamined tradeoff rather than a missed algorithm.
Day 1 — Map the loop and set up your tools
- Sort the reported questions into coding, debugging, system design, SQL and values, and mark which ones you have never attempted
- Write the questions for your recruiter call: screen format, allowed languages, onsite modules, and whether debugging runs in your local environment
- Install and verify your language runtime, test runner, HTTP client, JSON parsing and a timezone-aware date library
- Write a short scope summary of two systems you owned end to end
Deliverable: A categorized question list, a recruiter question list, and a working local environment with a passing sample test.
Day 2 — Card-and-gem purchase engine
- Implement can_purchase from colour gem balances, then purchase with state updates that change nothing when the purchase fails
- Add per-colour discounts from held cards and wild or gold gems that cover any shortfall, reusing one effective-cost function
- Write tests for an exact balance, one gem short but covered by a wild gem, a discount larger than the cost, and an unknown card
- Rebuild it from scratch within a time box while explaining each decision aloud
Deliverable: A tested purchase engine plus a list of the edge cases you would name in the interview.
Day 3 — REST API consumption and parsing prompts
- Write a script that follows every page of a paginated JSON endpoint and computes monthly spend and the peak month from ISO-8601 timestamps
- Add a consecutive-day same-merchant streak: one row per merchant per day first, then check that the dates are consecutive across month boundaries
- Solve the attribute-matching prompt: flag a candidate that shares at least k attributes with a flagged entry, first by brute force, then with an index keyed on attribute and value
- Build an evaluator for word expressions such as "5 times (20 plus 30)" with correct precedence and parentheses
Deliverable: Three working programs, each with a short note on complexity and the failure cases you tested.
Day 4 — SQL over transactions
- Write queries for the SQL bank topics: transaction counts, monthly spend totals, same-day transactions across consecutive months, and consecutive-day purchases at one merchant
- Practise the similarity query: count the attributes each candidate shares with a flagged entry and filter on a threshold
- Work through the worked exercise "Answer as-of balance queries over an append-only entry log" and check its answers against a naive reference
Deliverable: A query file covering each SQL topic, plus your completed as-of balance exercise.
Day 5 — Debugging an unfamiliar codebase
- Build or borrow a small multi-file service with tests, and plant bugs: an off-by-one day-of-week index, a missing leap-year rule, wrong holiday-override precedence, and a shared mutable default
- Fix them using only the failing tests: run the suite, read assertions and stack traces, state the cause, apply the smallest fix, and re-run everything
- Add one bug caused by a null response or a missing error handler in a transaction pipeline, and fix it without changing any public signature
Deliverable: A bug log recording, for each bug, the failing test, the root cause, the fix and the evidence that nothing else broke.
Day 6 — Payments system design
- Design the card authorization engine: webhook intake, balance check, spending rules and limits, and the response inside the prompt's latency bound, including what happens when a dependency is slow
- Design a peer-to-peer transfer service with double-entry ledger entries, idempotency keys and a locking strategy, using the "Make a charge endpoint safe under concurrent duplicate retries" drill for the key-claim path
- Sketch the gift card and rewards ledger or the spend-event workflow engine, then work through "Fan out ordered events to fifty thousand merchant endpoints" for delivery and backpressure
Deliverable: Two designs taken to schema and failure-mode depth, with idempotency and ledger decisions written next to each.
Day 7 — Values role-play and a full mock sequence
- Prepare STAR stories for critical feedback, fixing an unowned problem outside your scope, a decision made with incomplete requirements, and a project that missed its target
- Run a live role-play with a partner who plays the other side of a feature-date versus technical-debt conflict, and reach an agreed plan while staying in the scene
- Run a coding mock, a debugging mock and a design mock back to back, and note where you used the wrong approach for the round
Deliverable: Four rehearsed stories, notes from the role-play, and a short list of fixes from the mock sequence.