cswork / libraryOpen practice workspace

Interview guides · Ramp

Ramp Software Engineer Interview Guide

Ramp Software Engineer interview guide: the four reported rounds, practical coding, HTTP maze, multi-level class design and system design practice.

The 60-second brief

According to the source notes, Ramp's product combines corporate cards, expense management, accounts payable automation and procurement in a single platform. The notes describe software engineers there working on financial ledgers, transaction authorization, fraud detection and the user interfaces built on top of them, where money has to be handled exactly. You can see that context in the reported questions: banking ledgers, transaction aggregation by time bucket, a corporate card transaction pipeline, a rate limiter, and HTTP clients that have to cope with failing endpoints.

This guide covers the four stages candidates report for the Ramp Software Engineer role: an online assessment, a recruiter screen, a technical phone screen and a virtual onsite. It also covers the question categories reported for the role: practical coding and API traversal, multi-level class design, application and interface engineering in React, system design, SQL and behavioral. Each round section sets out what the sources describe and how to prepare for it. The practice set includes worked SQL, coding and design exercises on ledgers, balances and settlement ingestion.

Focus areas

  • Consume webhooks duplicated, delayed and out of order
  • Make every money-moving endpoint idempotent by key
  • Store money as integer minor units

Overview

The source notes describe Ramp as combining corporate cards, expense management, accounts payable automation and procurement in one platform. Software engineers are described as working on financial ledgers, real-time transaction authorization, fraud detection and the interfaces customers use. Money is involved throughout, so correctness around balances, duplicates and failures carries real weight, and it shows up directly in the reported interview questions.

The notes describe engineers owning features end to end, from specification through deployment and monitoring. The work spans backend services that handle asynchronous payment processing, frontend applications, and AI-assisted automation. The languages and tools the notes list include Python, TypeScript/JavaScript, Go or Java, React and PostgreSQL. The must-have skills they name are data structures, algorithms, object-oriented design, building and consuming REST APIs, and relational query design. Fintech background, React depth (for frontend and fullstack roles), and experience with message queues or AI development tools are listed as nice-to-haves.

For the interview, this means practical problems rather than puzzles. The reported questions include querying flight records by timestamp, following a chain of HTTP endpoints while handling 4xx and 5xx responses, and building ledgers, file systems and task managers in levels that each extend the last. They also include React builds such as a spreadsheet or calendar, and design questions such as a card transaction pipeline. The notes add that some technical rounds allow or expect an AI assistant, and that interviewers there check whether you understand the generated code. Prepare to write complete, tested code and to explain every line of it.

The Ramp interview process

Ramp candidates report 4 rounds · ≈ 3-5 weeks. The stages below are what candidates describe, not a published process.

Online Assessment

The source notes describe this stage as an asynchronous assessment or puzzle challenge built from practical coding tasks, and name CodeSignal as one format. Some candidates report starting with a recruiter screen instead, so ask which stage opens your process. Treat it as a test of execution. The questions reported for this role are practical: flight records, transaction totals by minute, string conversion, in-memory stores with TTL, and class builds that grow in levels. What pays off is writing correct, complete code against stated requirements and checking edge cases before you move on. Memorising puzzle tricks pays off less. When a task comes in parts that build on each other, finish and verify each part before you read the next. A bug left in an early part tends to break every later one.

What to demonstrate

  • Practical execution: turning written requirements into working, well-structured code within the time allowed
  • Edge-case handling and data modelling in state-heavy tasks such as ledgers, key-value stores and record lookups
  • Whether code written for an early part can take on later requirements without a rewrite

How to prepare

  • Work multi-level problems from the bank under a timer, such as Banking Tasks Class, the multi-level task manager and the in-memory cloud storage system. Finish and test each level before you read the next one
  • Before you submit any part, run it on an empty input, a single element, duplicate timestamps and an operation on a missing key
  • Rehearse the standard-library calls you lean on (sorting with a key, dictionaries of lists, binary search on a sorted list) so you do not spend assessment time looking them up

Recruiter Screen

The source notes describe a screening call about your background and your fit for the role. Use it to collect the information that shapes the rest of your preparation. Ask which track and level the loop is for (backend, frontend or fullstack), whether a system design round is included, whether any round uses an AI assistant, and whether live coding happens in your own editor. The reported question set spans backend coding, React application builds and system design. Knowing your track tells you where to spend the remaining days.

What to demonstrate

  • How clearly you summarise your background: what you built, what you personally owned and what changed because of it
  • How well your experience fits the track and level of the role
  • Your reasons for the role, and how you describe the work you want to do next

How to prepare

  • Prepare a short account of one or two projects covering what you built, what you owned and the outcome. The behavioral round's 'project you are most proud of' prompt draws on the same material
  • Ask which track and level the loop is calibrated to and which rounds follow, then plan your week from the answer rather than from the job posting
  • Ask about round formats: your own IDE or a shared editor, whether an AI assistant is allowed and with what limits, and which languages you can use

Technical Phone Screen

The source notes describe a live technical interview built around pair programming. They also say that several phone-screen problems involve querying timestamped records or nested API paths efficiently. Reported questions of that shape include locating a user from flight records at a given time, and following a chain of HTTP endpoints to a maze exit while handling 4xx and 5xx responses. Because this is pair programming, state your assumptions before coding. For example: what does a query at exactly a departure timestamp return, and should a 5xx be retried? Keep the code runnable at every step, and extend it cleanly when follow-ups arrive rather than starting over.

What to demonstrate

  • Practical execution: a working, tested solution rather than a sketch
  • The choice of structure for timestamped lookups, such as per-user records sorted by time with binary search instead of repeated linear scans
  • Resilient HTTP handling: telling permanent client errors apart from retryable responses (5xx, timeouts, 429 and 408), bounding retries and never revisiting a URL
  • How clearly you explain decisions while pairing

How to prepare

  • Solve User Location Query on Flight Data and Flight Records and User Location. Sort each user's flights by time, binary-search for the last event at or before the query time, and say explicitly what a query during a flight returns
  • Solve URL Maze with Error Handling and Clue-Driven HTTP Sequencing with a requests-style client. Use a visited set, bounded retries with backoff for 5xx, timeouts, 429 Too Many Requests and 408 Request Timeout (honouring Retry-After when it is sent), no retry on other 4xx, and an explicit exit condition
  • Practise in your own editor with the language docs open. The source notes report that live coding is often open-book, but confirm this for your loop

Virtual Onsite Loop

The source notes describe several technical rounds, including coding and system design, plus a behavioral interview with a hiring manager. The source does not map individual questions to onsite rounds. The reported questions for the role fall into these categories: multi-level class design (a banking ledger, a file system with lock/key security, a task manager); application engineering in React (a calendar day view, a spreadsheet with formula dependencies, an email client, Wordle or tic-tac-toe); and architecture (a corporate card transaction pipeline, a sliding-window rate limiter). The notes also say some technical rounds allow or expect an AI assistant with a prompt limit. If yours does, be ready to explain the generated code line by line. Weight your preparation by the track and level the recruiter gave you.

What to demonstrate

  • Pragmatic system design: simple, workable designs that address concurrency, failure modes and data consistency without over-engineering
  • Clean, modular class design that absorbs each new level of requirements
  • Where an AI assistant is allowed: directing it well, catching its bugs and explaining how the generated code works
  • Product thinking in the hiring-manager conversation: connecting technical choices to user and business outcomes

How to prepare

  • Design the corporate card transaction pipeline end to end: authorization, settlement events, fraud signals and notifications. Add idempotency keys and a plan for duplicate or out-of-order events, then work this guide's ledger and settlement exercises
  • On a frontend or fullstack track, build the spreadsheet in React (dependency graph, recompute order, cycle detection) and the calendar day view (grouping overlapping events into columns)
  • Rehearse the reported behavioral prompts, including how you use AI in daily work and how you check its output
  • Keep one fixed set of facts per project (scale, team, what you owned) so the design and behavioral rounds hear the same account

Where candidates lose points

Hard-coding level 1 of a multi-level class problem so that the next level forces a rewrite

Reported problems such as the banking ledger, file system and task manager add requirements in levels: transfers, scheduled payments and top accounts; copies and locks; expiring tasks. Model the entities first, for example accounts or files as objects in a dictionary keyed by id, with an operation log if history might matter later. Keep each operation a small method that validates its input and returns an explicit result for failures such as a missing account or insufficient funds. Do not bake assumptions like 'a balance only changes on deposit' into shared code. Rerun the earlier levels' tests before starting a new one.

Following the HTTP maze on the happy path only

The maze and clue-driven request questions add follow-ups about 4xx and 5xx responses, so decide the policy before you write the loop. Most 4xx codes (400, 401, 403, 404) mean the request itself is wrong: record it and stop, or skip that branch, rather than retrying. Two 4xx codes are the exception: 429 Too Many Requests and 408 Request Timeout are retryable, so back off and retry them, waiting at least as long as a Retry-After header asks. The source notes list rate-limit backoff logic among the API-traversal topics. A 5xx or a timeout is often transient: retry a bounded number of times with backoff. Keep visited URLs in a set so a cycle cannot loop forever, parse every response defensively, and write the exit condition as an explicit check.

Submitting AI-generated code you cannot explain line by line

The source notes say that where an AI assistant is allowed, interviewers test your understanding of the code, not just its output, and that prompts may be capped. Write one focused prompt per piece of work. Read what comes back before you run it, and test it on edge cases you chose yourself. Be ready to say why it used a heap, a dictionary or a particular loop bound, and fix off-by-one errors and missing cases yourself instead of re-prompting.

Leaving a coding task partly working while polishing one part of it

The source notes describe completeness as critical: fully passing solutions are expected in coding stages. Get a correct, simple version of every required part running first, then optimise. For the minute-bucket aggregation, for example, floor each timestamp to its minute and sum into a dictionary before you think about output ordering or streaming. Pace yourself across the parts of a multi-part task instead of spending most of your time on the first.

Designing the card transaction pipeline or ledger without guarding against money being moved twice

In design questions like the card pipeline, the ledger or the rate limiter, point out where a retried request, a duplicated settlement event or two concurrent writes could apply the same effect twice, and close each gap. Use idempotency keys enforced by a unique constraint, store amounts as integer minor units, derive balances from ledger entries, and use row locks or conditional updates on hot rows. Keep the rest as simple as the requirements allow. The source notes say interviewers weigh practical simplicity over over-engineering.

Questions & practice

Write a function that accepts a list of transaction records (timestamps and amounts) and returns aggregate transaction totals broken down by minute buckets.

Medium · data structures and algorithms

Approach

  1. Walk one small example through your approach before writing the whole thing.
  2. Name the brute-force solution and its complexity before improving on it.
  3. 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?
  • How does this change if the input no longer fits in memory?

Given a starting URL, make chained HTTP requests where each response c…

Medium · data structures and algorithms

Given a starting URL, make chained HTTP requests where each response contains clues for the next endpoint to follow a "maze exit" puzzle, with progressive follow-ups handling 4xx and 5xx error codes.

Approach

  1. Walk one small example through your approach before writing the whole thing.
  2. Choose the data structure from the access pattern, not from familiarity.
  3. 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?

Convert every snake_case string token into camelCase across a sentence while preserving non-target formatting and punctuation.

Medium · data structures and algorithms

Approach

  1. Choose the data structure from the access pattern, not from familiarity.
  2. Walk one small example through your approach before writing the whole thing.
  3. Name the brute-force solution and its complexity before improving on it.

Follow-up

  • Which test case would catch an off-by-one here?
  • How does this change if the input no longer fits in memory?

Implement a function get_user_location(time) and get_user_with_most_flights given a dataset of flight records containing departure/arrival airports, user IDs, and timestamps.

Medium · data structures and algorithms

Approach

  1. Restate the input: its shape, its size, and what is guaranteed about it.
  2. Name the brute-force solution and its complexity before improving on it.
  3. Choose the data structure from the access pattern, not from familiarity.

Follow-up

  • Which test case would catch an off-by-one here?
  • How does this change if the input no longer fits in memory?

Derive per-account balances and catch unbalanced transactions

Easy · aggregation · double-entry · streaming

You are given ledger_entry rows streamed in entry_id order: transaction_id, account_id, direction (debit or credit), amount_minor (a positive int64), currency, business_date. Up to 500 million rows, at most 20 million distinct (account_id, currency) pairs, and the entries of one transaction are contiguous in the stream. In a single pass with no re-reads, return the closing balance per (account_id, currency) and the transaction_id of every transaction whose entries do not sum to zero within each currency. State your time and space bounds.

Approach

  1. Normalise the sign at read time from direction, not from the amount: signed = +amount_minor for debit, -amount_minor for credit (state which convention you picked). The schema constrains amount_minor > 0 precisely so the sign lives in exactly one place.
  2. Hold one hash map keyed (account_id, currency) to an int64 running total. Twenty million keys at 16 bytes of payload plus map overhead is order 1 GB in most runtimes — quote the number, and offer the fallback: partition the stream by hash(account_id) % P and run P passes for 1/P of the memory.
  3. Ride the zero-sum check on the same pass. Because a transaction's entries are contiguous, keep a tiny currency -> int64 map for the current transaction_id only, test it against zero on the boundary and at EOF, then clear it. That is O(currencies in one transaction), typically one or two.
  4. Bound the arithmetic explicitly. Int64 holds about 9.22e18, so overflowing one account across 500 million entries needs an average of 1.8e10 minor units per entry — safe here, but use a checked add so an adversarial file fails loudly rather than wrapping.
  5. Complexity: O(n) time, O(distinct account-currency pairs) space, one sequential pass, no sort. The zero-sum check adds no asymptotic cost, which is the argument for doing it here rather than in a second job.

Worked solution (20 min)

  1. Write the sign rule down in one sentence before any code, naming which side debit is positive on, and apply it at read.
  2. Implement with two maps — balances: (account_id, currency) -> int64 and txn: currency -> int64 — plus the current transaction_id.
  3. On a change of transaction_id, assert every currency in txn sums to zero, record the id if not, then clear.
  4. Feed a fixture: one 2-entry transaction that balances; one 4-entry transaction with USD and JPY legs that balances within each currency; one 3-entry transaction off by a single minor unit.
  5. Re-run with the entries shuffled inside each transaction to prove the result is order-independent within a transaction.

Expected result: The balances map is identical under any within-transaction ordering; exactly the third transaction is reported; the mixed-currency transaction is accepted, because zero-sum is required per currency, not across the transaction.

Verify it yourself

  • Across the whole file, the sum of every account balance is exactly 0 per currency whenever no transaction is flagged.
  • Two runs over the same file produce byte-identical output.
  • Swapping the int64 accumulator for binary64 makes a fixture with amounts above 2^53 minor units disagree — that is a failing test, not a review comment.

Follow-up

  • Entries of a transaction are no longer contiguous. What does the zero-sum check cost now, and which is cheaper: buffering open transactions or an external sort on transaction_id?
  • How would you produce the same balances as of an arbitrary business_date without a second full scan?
  • The job is restarted after a crash halfway through the file. What makes the second run produce identical output?

Make a charge endpoint safe under concurrent duplicate retries

Easy · idempotency · unique constraints · read committed

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.

Approach

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. 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-up

  • 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?

Store multi-currency amounts and prove each transaction balances

Easy · minor units · normalisation · constraints

ledger_entry holds entry_id, transaction_id, account_id, direction (debit or credit), amount_minor (bigint, CHECK > 0), currency char(3), source_type, source_id, business_date and posted_at. The service posts in JPY, USD and KWD. Specify the column types for money and the reference table that carries each currency's ISO 4217 minor-unit exponent, and say why that exponent must not be a constant in application code. Then write the query that returns every transaction_id whose entries fail to sum to zero within each currency, ordered by the largest absolute imbalance.

Approach

  1. State the representation: amount_minor is an integer count of minor units and the scale lives with the currency, not in the column. int4 tops out at 2,147,483,647 minor units, which is about 21.5 million units of a 2-exponent currency, so bigint, and the currency code travels on every row that carries an amount.
  2. Normalise the scale into currency(code char(3) primary key, exponent smallint, name) and join it only at display and parse boundaries. The exponent is 0 for JPY and KRW, 2 for USD and EUR, 3 for KWD, BHD, JOD, OMR and TND, so a hard-coded divide by 100 is wrong by 100x for JPY and by 10x for KWD, in opposite directions.
  3. Rule out binary floating point outright: IEEE 754 binary64 cannot represent 0.1, so repeated accrual drifts by a few minor units that later appear as reconciliation breaks. If a fixed-scale decimal is used instead, pin the scale per currency and name the single place rounding happens.
  4. Write the balance check as a grouped aggregate: sign the amount with a CASE on direction, GROUP BY transaction_id, currency, and HAVING the signed sum <> 0. The sign belongs in direction, and the CASE is the one place it becomes arithmetic.
  5. Then order in a second layer, because the ordering the question asks for cannot be written on the grouped query itself. PostgreSQL lets an output alias stand alone in ORDER BY but resolves anything inside an expression against the input columns, so ORDER BY abs(imbalance_minor) DESC raises SQLSTATE 42703, column "imbalance_minor" does not exist, while the bare ORDER BY imbalance_minor DESC is accepted and sorts signed rather than absolute. Wrap the grouped query and order by abs() outside it, or repeat the aggregate as ORDER BY abs(SUM(CASE ...)) DESC, which is legal because aggregates are allowed there.
  6. Group per (transaction_id, currency) rather than per transaction, because a cross-currency movement balances only within each currency, bridged by an explicit FX position account whose rate, source and timestamp are stored on the transaction.
  7. Note that no CHECK constraint can express this, since it spans rows: enforce it at write time with a DEFERRABLE INITIALLY DEFERRED constraint trigger that fires at commit, or by routing every posting through one function, and keep this query as the independent audit.

Worked solution (15 min)

  1. Define currency(code, exponent, name) and seed JPY 0, USD 2, KWD 3; declare amount_minor bigint NOT NULL CHECK (amount_minor > 0) and currency char(3) REFERENCES currency(code).
  2. Write it in two layers so it runs: SELECT transaction_id, currency, imbalance_minor FROM (SELECT transaction_id, currency, SUM(CASE WHEN direction = 'credit' THEN amount_minor ELSE -amount_minor END) AS imbalance_minor FROM ledger_entry GROUP BY transaction_id, currency HAVING SUM(CASE WHEN direction = 'credit' THEN amount_minor ELSE -amount_minor END) <> 0) b ORDER BY abs(imbalance_minor) DESC.
  3. Post one clean capture, one deliberately unbalanced transaction, and one cross-currency transfer with an FX position account, then run the query.
  4. Convert 1000 JPY and 1000 KWD through the exponent table in both directions and confirm the round trip is exact.

Expected result: Exactly one row: the deliberately unbalanced transaction, with a signed imbalance_minor equal to the amount omitted. The clean capture and both single-currency sides of the FX transfer return nothing.

Verify it yourself

  • The cross-currency transaction produces two groups, one per currency, and neither appears in the output.
  • Flipping one entry's direction makes imbalance_minor exactly twice that entry's amount, which confirms the sign convention.
  • Inserting an entry with amount_minor = 0 or a negative value is rejected by the CHECK rather than by application code.
  • Execute the statement rather than reading it: ordering the grouped query directly by abs(imbalance_minor) fails with 42703, which is why the abs() sits outside the subquery.

Follow-up

  • A transaction has a USD leg and a JPY leg. Which accounts appear, and where does the rounding residual land?
  • How would you enforce the zero-sum rule at write time without serialising all postings behind one lock?
  • Someone proposes storing a display amount alongside amount_minor. What goes wrong at the first rate change or rounding rule change?

Build a multi-tier banking ledger system across progressive levels (creating accounts, deposit/withdraw/transfer, handling scheduled payments, and computing top accounts by activity).

Medium · system design and architecture

Approach

  1. Name the failure you are designing for, then the recovery path.
  2. Fix the scope first: who calls this, how often, and what they do when it fails.
  3. State the consistency you need, and where you are willing to be stale.

Follow-up

  • What breaks first when traffic grows ten times?
  • What would you drop to keep the system up under load?

Design and implement a basic file system class supporting FILE_UPLOAD, FILE_GET, FILE_COPY, and lock/key security mechanisms.

Medium · system design and architecture

Approach

  1. Name the read and write paths separately; they rarely have the same bottleneck.
  2. Choose a partition key and say what query it makes expensive.
  3. State the consistency you need, and where you are willing to be stale.

Follow-up

  • How does this behave when that dependency is down for an hour?
  • What breaks first when traffic grows ten times?

Implement an in-memory JavaScript spreadsheet engine capable of adding, editing, and deleting columns, with dynamic formula dependency resolution across cells.

Medium

Approach

  1. Say what you would check first and why it is the highest-information step.
  2. State your assumptions explicitly before working the problem.
  3. Clarify what is being asked and what a complete answer contains.

Follow-up

  • What assumption would you test first?
  • How would you know your answer was wrong?

Ingest daily settlement files idempotently and restartably

Easy · idempotency · batch ingestion · partitioning · restartability

A processor delivers one settlement file per business date containing 10 to 50 million lines, each with an external reference, a signed amount in minor units, a currency, a fee and the processor's settlement date. Files arrive late, are sometimes re-sent byte-identical, and are sometimes re-sent corrected under the same filename. Design ingestion: how a line becomes a settlement_line row, how a re-send avoids duplicating 50 million rows, how a correction supersedes the earlier version without deleting it, and how a run that dies at line 30 million resumes. State your throughput target.

Approach

  1. Treat the file as a versioned object rather than a stream of events. Key a file record on (processor_id, business_date, content_sha256): an identical re-send matches the digest and becomes a no-op in O(1), while a corrected re-send has a different digest and becomes file_version 2, which supersedes version 1 by stamping superseded_at on its lines rather than deleting them. Deleting them destroys the evidence for any break already raised against version 1.
  2. Give lines two levels of identity. UNIQUE (file_version_id, line_no) makes a resumed chunk unable to double-insert. Deduplication on business content must not collapse (external_reference, amount_minor, currency) into one row, because two identical genuine charges in one day are ordinary; if you need a business-level key, it has to include the line ordinal.
  3. Make restart cheap by committing in chunks of about 10,000 lines and recording a high-water line_no per file_version, so a crash costs at most one chunk. Smaller chunks mean more commit and WAL overhead; larger chunks mean more re-work after a failure. Pick from measured commit cost, not from a round number.
  4. Set the throughput target from the loading mechanism: a bulk COPY of narrow rows runs at order 10^5 rows/s per connection where row-by-row INSERT runs at order 10^3 to 10^4, so 50 million lines is minutes rather than hours. Parallelise by splitting the file into byte ranges aligned to line boundaries, one worker and one checkpoint per range.
  5. Partition settlement_line by business_date. A full re-ingest then rebuilds one partition instead of issuing a 50-million-row DELETE, and retention becomes a partition DETACH rather than a long-running vacuum problem.
  6. Validate before publishing the version: compare the file trailer's control totals against SUM(amount_minor) over the loaded rows, and fail the run on mismatch. A truncated file that loads cleanly otherwise looks exactly like a day when the processor sent less money.

Worked solution (30 min)

  1. Generate a 10,000,000 line file, compute its SHA-256, and load it with COPY into a staging table partitioned by business_date, committing every 10,000 lines and recording the high-water line number.
  2. Ingest the identical file again and assert zero new rows, with no work done beyond the digest comparison.
  3. Alter 100 lines, re-ingest under the same filename, and assert a new file_version supersedes the first while version 1 rows remain fully readable.
  4. Kill the worker at line 4,000,000, restart it, and count the final rows.

Expected result: COPY sustains order 10^5 narrow rows per second per connection, so 10 million lines land in roughly one to two minutes on a single connection and scale close to linearly with byte-range parallelism. The identical re-send costs one digest comparison. The crash-restart run ends with exactly 10,000,000 rows: no duplicate chunk, no gap at the restart boundary.

Verify it yourself

  • UNIQUE (file_version_id, line_no) exists and is what makes the resumed chunk safe, rather than the worker's own bookkeeping.
  • Two lines identical in reference, amount and currency both survive ingestion.
  • Control totals from the trailer equal SUM(amount_minor) for the version, and a deliberate truncation fails the run instead of opening breaks.

Follow-up

  • The same file starts ingesting twice concurrently from two workers. What stops the second one?
  • The corrected file has fewer lines than the original. Which lines disappeared, and how does a break already raised against one of them get resolved?
  • How do you know the file is complete rather than still being written by the transfer?

Duplicate captures appear only in production, roughly weekly

Hard · idempotency · race condition · read committed · concurrency

About once a week one payment is captured twice. The idempotency path is: SELECT id, response_body FROM idempotency_key WHERE scope = $1 AND key = $2; if no row, call the processor; then INSERT. The table has UNIQUE (scope, key). Logs for each duplicate show one successful capture pair and one HTTP 500 carrying SQLSTATE 23505. A 200-iteration sequential test passes, and a 50-thread version passes on a laptop but fails on the production-sized cluster. Explain why, and give the fix.

Approach

  1. Read the 23505 as evidence, not as noise. A unique violation on the INSERT proves two requests both passed the SELECT and both reached the INSERT, which means both had already called the processor. The duplicate charge happened before the constraint fired. The constraint is reporting the race; it is not causing it, and anyone who treats the 500 as the bug fixes the wrong thing.
  2. Name the interleaving precisely. Under READ COMMITTED each statement takes a fresh snapshot, so two concurrent requests with the same key can both run the SELECT before either INSERTs and both see zero rows. Raising the isolation level does not fix check-then-act by itself, because at SELECT time the first transaction has written nothing to conflict with; SERIALIZABLE only converts the race into a 40001 abort that the code must then retry.
  3. Explain the reproduction gap rather than calling the bug rare. The window is the duration of the processor call: milliseconds against a stub on a laptop, hundreds of milliseconds against a real processor. Production retries are also correlated, since a client timeout produces a second request at a predictable delay, while a thread-pool test fires all 50 within microseconds and lands them on the same side of the window. The local test is not exercising the window at all.
  4. Restructure so the database picks the winner before any side effect. INSERT the key first with ON CONFLICT (scope, key) DO NOTHING RETURNING id. A returned id means this request owns the effect and may call the processor. No returned row means another request owns it, and note that RETURNING yields nothing on conflict, so the loser must then SELECT the existing row explicitly. Mutual exclusion now lives in one atomic statement and the window is gone.
  5. Give the loser something to read. If the winner is still in_progress, the loser must neither error nor perform the effect: it polls the row inside the caller's timeout budget and replays response_status and response_body once status is completed, or returns 409 when request_fingerprint differs. Without this, deduplication turns a successful payment into a visible failure.
  6. Close the crash window separately, because the atomic insert does not cover it. If the winner dies after calling the processor and before writing completed, the row stays in_progress with a stale locked_at. Recovery must query the processor for that key or client reference rather than assume either outcome, which is why the processor's own idempotency key has to be the same value, generated once by the caller and reused on every attempt.

Follow-up

  • The same key arrives with a different request_fingerprint. What do you return, and why is returning the cached response wrong?
  • What is your locked_at staleness threshold, and what does the sweeper do when it finds an expired one?
  • Write the test that fails on the laptop. What do you have to inject to make the window observable there?

Suggested practice checklist

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 — Multi-level class design: the banking ledger

  • Build the reported banking ledger in levels: create accounts; deposit, withdraw and transfer, each returning an explicit failure result; scheduled payments; top accounts by activity. Finish and test each level before starting the next
  • Repeat the pattern on the bank's multi-level task manager or in-memory cloud storage problem, keeping entities in dictionaries keyed by id
  • Write down which level-1 choices made later levels easy and which forced changes

Deliverable: Two multi-level implementations with tests for each level, plus a short list of design choices that survived every later level.

Day 2 — In-memory stores: TTL, eviction, snapshots and locks

  • Implement the in-memory key-value database with TTL expiration and snapshots. Store the expiry with each value, treat a key as missing once the current time reaches its expiry, and decide whether a snapshot copies state or records versions
  • Solve TTL Cache with Eviction using a hash map plus a min-heap keyed by expiry, skipping stale heap entries for keys that were overwritten
  • Implement the file system class with FILE_UPLOAD, FILE_GET, FILE_COPY and lock/key checks. Test copying onto an existing name and access with the wrong key

Deliverable: A TTL store with snapshot support, a bounded TTL cache, and a file system class, each with edge-case tests.

Day 3 — API traversal and timestamped lookups (the phone-screen patterns)

  • Solve URL Maze with Error Handling and Clue-Driven HTTP Sequencing with a requests-style client: visited set, bounded retries with backoff for 5xx, timeouts, 429 and 408 (honouring Retry-After), no retry on other 4xx, explicit exit check
  • Solve API Interaction With Hashmap and DFS, visiting each endpoint once
  • Solve the flight-records questions: get_user_location(time) with per-user sorted events and binary search, and get_user_with_most_flights with a counter. Settle the 'in the air' case and ties out loud
  • Pair with someone, or record yourself, and narrate assumptions before each function

Deliverable: Working HTTP traversal code with a written retry policy, and a tested flight-records solution that states its complexity.

Day 4 — Data processing and SQL

  • Convert snake_case tokens to camelCase across a sentence, preserving punctuation and leading or trailing underscores. Test tokens that are all underscores and tokens with numbers
  • Aggregate transaction totals by minute bucket by flooring each timestamp to its minute. Handle empty input and out-of-order records
  • Work the bank's Recurring Weekly Spend and Optimizing SQL Query Performance questions, then do this guide's worked SQL exercise on multi-currency amounts and unbalanced transactions (drill-sql-2)

Deliverable: Tested string and bucketing functions, and a SQL balance check that you actually ran against a small fixture.

Day 5 — Application and interface engineering

  • Frontend or fullstack track: build the spreadsheet with formula dependencies. Keep a dependency graph, recompute dependents in topological order and reject cycles
  • Build the calendar day view in React: sort events by start time, group overlapping events, and assign columns so overlapping events sit side by side
  • Build Wordle or tic-tac-toe with state held in one place and derived values computed rather than stored
  • Backend track: spend the day on a second multi-level problem and the bank's Implement a Clean In-Memory Transaction Ledger instead

Deliverable: One complete interface build (or backend equivalent) that runs, with a note on how its state is structured.

Day 6 — System design: money movement and rate limiting

  • Design the corporate card transaction pipeline: authorization, settlement events, fraud signals and notifications. Name where duplicates and out-of-order events arrive and how idempotency keys stop them
  • Design and implement a sliding-window rate limiter per user, then explain what changes when it runs across several servers
  • Work this guide's settlement-ingestion design exercise (drill-design-4), then the idempotent charge endpoint (drill-sql-1) and the duplicate-capture debugging question (drill-debugging-5)

Deliverable: A written pipeline design with its failure modes and consistency choices, and a working rate limiter.

Day 7 — AI-assisted coding, behavioral stories and a full mock

  • Build a small data-processing module with an AI assistant under a self-imposed prompt cap, explaining each generated block out loud and fixing its bugs yourself
  • Prepare a story for each reported behavioral prompt: your proudest project, a project that fell behind, a disagreement with leadership or product, how you use and verify AI, and a customer pain point or incident
  • Do this guide's worked coding exercise on per-account balances (drill-coding-3) as a mock under a timer, narrating as you would to an interviewer
  • List your questions and constraints for the recruiter: track, level, round formats, tool policy

Deliverable: A recorded AI-assisted session you can explain line by line, five behavioral stories with fixed project facts, and one completed mock.

The estimate and the day you knew

The source notes say the onsite includes a behavioral interview with a hiring manager. The reported prompts cover ownership, trade-offs and business impact, a schedule slip, disagreement with leadership or product, AI in your daily workflow, and customer pain points or incidents. For each one, prepare one specific story: the decision you made, the evidence behind it and the outcome. Keep the project facts consistent with anything you described in a design round.

  1. Describe a time when a project you were leading fell behind schedule. How did you realign stakeholders, reprioritize scope, and drive execution?
  2. Describe a situation where you had a strong technical disagreement with engineering leadership or product managers. How did you approach the conversation and reach alignment?
  3. How do you integrate AI into your daily development workflow, and how do you evaluate and verify the accuracy of AI-generated output?

Tell me about the project you are most proud of: what was your specific ownership, what trade-offs did you make, and what was the business impact?

Medium · behavioural and engineering judgement

Approach

  1. State the situation in two sentences and spend the rest on the reasoning.
  2. Give the blast radius: what could have broken, and what you measured.
  3. 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 have you navigated a complex customer pain point or technical incident in the past? What would you do differently in hindsight?

Medium · behavioural and engineering judgement

Approach

  1. Name the disagreement and how you resolved it with evidence.
  2. Give the blast radius: what could have broken, and what you measured.
  3. Close with what you would do differently, concretely.

Follow-up

  • How did you know your change caused the improvement?
  • What did you decide not to do, and why?

Force an implicit timeout behaviour into an explicit decision

Medium · fail open · risk decisioning · decision records

The risk decision service has an 80 ms p99 budget inside a roughly 2 s caller timeout. Today, when its feature store is unavailable, the timeout handler returns approve. Nobody chose that; it is what the code does. You need a real decision: fail open, fail closed, or refer, potentially differing by amount band. Describe a time you turned an accidental behaviour into an owned decision. State who had to be in the room, the data you brought, what you did when nobody wanted to own it, and where the decision was recorded so it outlived you.

Approach

  1. The probe is whether you can drive a cross-functional decision rather than escalating and waiting. Lead with the framing that makes it undeniable: this is already a product decision, it is currently being made by an exception handler, and the only question is whether anyone reviews it.
  2. Bring the two losses side by side instead of arguing a principle. Fail open costs expected fraud loss on approved-but-should-have-declined volume during the outage; fail closed costs declined good payments, which is lost revenue plus customer harm and a support queue; refer costs manual review capacity, which is a headcount number and saturates within minutes at 3,000 decisions per second. Give each as a rate per minute of outage using real volume.
  3. Propose the banded answer as the default, because the two losses cross over at an amount: below some threshold the expected fraud loss is smaller than the expected decline loss, above it the reverse, and the crossover is computable from observed fraud rate by band. That converts a values argument into an arithmetic one.
  4. Name the attendees by the decision they own, not by title: whoever carries fraud loss, whoever carries approval rate, and whoever staffs manual review. Three people who can each say yes is a decision; eight people who can each say no is a meeting.
  5. Say what you did when ownership was contested. A strong answer has a forcing function: propose a default in writing with a review date and state that it ships unless someone objects, which converts inaction into consent rather than into another meeting.
  6. Record it where the code can find it: the decision, its date, its owner, the amount thresholds, and a test asserting the fallback behaviour, so the next engineer reading the timeout handler learns it was chosen. A wiki page nobody links from the code is the generic answer.

Follow-up

  • The feature store is degraded rather than down and the model is scoring on stale features. Is that the same decision?
  • How do you stop the banded thresholds from silently rotting as fraud patterns shift?
  • Nobody objects to your written default, and six months later there is an outage and a loss. Who owns it?