cswork / libraryOpen practice workspace

Interview guides · Circle

Circle Software Engineer Interview Guide

Circle Software Engineer interview guide: six reported rounds, coding, payments system design and stack questions, plus a 7-day prep plan.

Interviewing at Circle

  • Practice bank: 11+ questions
  • Rounds: 2
  • Typical prep: 1–2 weeks
  • Interview reports: 1

Interview rounds

RoundQuestions
Online Assessment9
Technical Screen2

How hard is the Circle Software Engineer interview?

DifficultyShareQuestions
Easy0%0
Medium64%7
Hard36%4

What Circle actually tests for

TopicShareQuestions
Coding & Algorithms36%4
System Design36%4
Behavioral & Leadership9%1
Data Manipulation (SQL/Python)9%1
Software Engineering Fundamentals9%1

The 60-second brief

Circle builds financial infrastructure around USDC, a fully reserved digital currency, together with the Circle Payments Network and Arc, a blockchain platform built for enterprises. Software Engineers build microservices and APIs, connect platform services to banks and blockchain protocols, and work under financial and security regulation. That is why idempotency, ledger consistency and auditability of money movement come up again and again in the reported questions.

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

What a Software Engineer does at Circle

According to reports, the day-to-day work covers microservices in Java or Go that handle API traffic, ledger entries and transaction state. It also covers public and internal REST APIs that hide the details of blockchain networks and banking rails such as ACH and wire transfers, and services that mint, burn and transfer USDC. Engineers work with product managers, security engineers, compliance analysts and operations teams, because the software has to meet financial and security regulation.

For interview preparation, this means money-movement correctness runs through the reported questions. Examples include exactly-once payment processing, a ledger that has to stay consistent and auditable under concurrent writes, reconciliation against bank records, and custody of signing keys. The coding topics most often reported are in-memory database design with CRUD operations and TTL expiry, plus classic data-structure and algorithm problems.

The reported requirements list professional Java or Go experience, database design and production microservices in a cloud environment as must-haves. MERN stack depth for full-stack positions, and prior payments or blockchain work, are listed as nice-to-haves. Ask your recruiter which of these your loop is built around before you decide where to spend your preparation time.

The Circle interview process

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

Recruiter Conversation

Before anything technical happens, someone has to decide which rung of the ladder your loop is calibrated to, and that decision sets the bar for every round after it. It comes from how you describe scope, not from your title, because titles do not convert cleanly between companies. The weak version of the answer is team size and years. The strong version names the largest change you shipped where nobody reviewed the design, what would have broken if you had been wrong, and what you were paged for. Get the level said out loud on this call, because the range and the loop both follow from it.

What it evaluates

  • Whether the scope in your own account maps onto a level the team actually has an opening at, so a mismatch ends the process cheaply rather than after four interviewers have spent a day
  • Whether your title needs re-mapping: the same word describes very different amounts of independent decision-making at a twenty-person company and a ten-thousand-person one
  • Whether your compensation expectation can be filled at that level in the structure the role pays in, which is why the number gets asked for before any engineer is scheduled

How to prepare

  • Write down two changes from the last two years: the largest one you designed with nobody reviewing the design, and the largest one where someone more senior did. Lead with the first when scope comes up, and be ready to say which parts of the second were yours
  • Ask which level the loop is calibrated to and what changes at the level above it, then plan your weeks from that answer rather than from the posting
  • Settle a total-compensation range beforehand with the split named, base against bonus against equity and its vesting period, so a question about numbers gets a number instead of the word market

Automated Coding Assessment

Candidates describe an online assessment of core algorithm and data-structure knowledge. Candidates describe it as a filter, with a time limit and a competitive cutoff. The reports do not say which problems it uses, so prepare for the format rather than for specific questions. Stateful simulation problems appear in the question bank for this role: an in-memory database with TTL and backup, a banking system with transfers, a recipe store with CRUD and case-insensitive lookups, and resource operations keyed by formatted string IDs. They make good timed practice because they reward careful reading and tests on boundary cases. Read the input bounds before you choose a structure, choose it from the full set of operations you must support, and keep a small harness running the examples plus empty, duplicate and boundary cases as you go.

What it evaluates

  • Whether the solution is correct on inputs the worked examples do not show, such as empty input, duplicates and boundary values
  • Whether the chosen structures support every required operation within the stated input bounds
  • Whether a working answer is submitted before the time limit, rather than an optimal one left unfinished

How to prepare

  • From a blank file, build an in-memory key-value store with set, get, delete, TTL expiry checked on read, and a backup/restore that preserves the remaining TTL. Then add one new requirement without restructuring the code
  • Implement a banking system with deposits, transfers, idempotent operation IDs and a top-K query for outgoing volume, and write tests for insufficient funds, unknown accounts and repeated IDs
  • Write the harness before the solution: feed in the worked examples plus an empty case and a single-element case, and print expected against actual

Live Coding Sessions

Candidates describe interactive coding with Circle engineers on a shared platform. The reports do not say which problems appear in these sessions, but the reported algorithm questions make a useful practice set: maximum-sum contiguous subarray over transactions, cycle detection in a graph of accounts and transfers, the best conversion path through exchange rates when arbitrage loops exist, a concurrent LRU cache, and a per-IP rate limiter. The question bank adds merging connected accounts with union-find and returning the N most frequent event keys. With someone watching, silence is the expensive failure. State the approach and its complexity, name the edge cases you plan to handle, then write code while keeping your commentary in step with what you type.

What it evaluates

  • Whether you can explain why the chosen algorithm is correct, for example why a reachable cycle whose rate product exceeds 1 makes the best exchange-rate path unbounded
  • Edge-case handling that follows from the domain: an all-negative transaction array, self-transfers, disconnected accounts, rates of zero
  • Whether a hint gets tested against a concrete input instead of being accepted or argued with on reflex

How to prepare

  • Solve the exchange-rate question by turning rates into -log weights and running Bellman-Ford. Then explain aloud how a negative cycle under those weights maps to an arbitrage loop (a cycle whose rate product exceeds 1) and what the function should return when one is reachable
  • Implement an LRU cache with a hash map and a doubly linked list, then make it safe for concurrent use: first with one lock, then explain what lock striping would change and what it would cost
  • Write a per-IP rate limiter twice, as a token bucket and as a sliding window, and say how each one cleans up memory for IPs that have gone idle
  • Practise at least one problem each day in a plain browser editor without autocomplete, in case the shared platform has none

Technical Rounds

Reports describe technical rounds focused on your primary stack: Java, Go or the MERN stack. For full-stack and frontend-leaning roles, they also describe hands-on exercises where you modify existing code. The reported stack questions include how the Node.js event loop handles high volumes of asynchronous I/O, refactoring a React transaction-history component to cut re-renders and prevent memory leaks, schema design for nested payment data with frequent reads and writes, adding filtering and pagination to an existing endpoint without breaking current clients, and error handling with transactional rollback in a multi-step Express service. In a modification task, spend as much effort on finding the right place to change and proving you broke nothing as on the new feature.

What it evaluates

  • Whether you can explain your stack's runtime model precisely: event-loop phases and blocking in Node, goroutines and channels in Go, or threads and locks in Java
  • Whether a change to an existing endpoint keeps old requests and response shapes working
  • Whether a multi-step write leaves consistent state when a middle step fails

How to prepare

  • Confirm with your recruiter which stack these rounds use, and spend your time on that one
  • Take a small Express and React app, or a Java or Go service, and add a filter and cursor pagination to a list endpoint. Keep every new parameter optional, keep the default response unchanged, and add a test that sends the old request
  • Implement a multi-step transfer inside a database transaction, force a failure at each step in turn, and confirm nothing partial is committed. For steps outside the database, write down the compensating action
  • Find the cause of an unnecessary re-render or a leaked subscription in a component that receives frequent updates, and explain the fix before you apply it

Project Deep-Dive

Candidates describe this round as a discussion of past projects and system design. Prepare in two directions. First, a project you can take down to the code you changed and the alternatives you rejected. Second, design reasoning on the payments problems reported for this role: a highly available payments engine with exactly-once processing, a ledger that handles concurrent writes while staying consistent and auditable, ingesting event streams from several blockchain networks, managing cryptographic keys for signing transactions, and reconciling internal records with bank partners. These questions share one habit you can practise: name the failure you are designing for (retries, timeouts, partial failure, duplicate delivery) before you draw any boxes.

What it evaluates

  • Whether you separate your own decisions from the team's, and can defend each against the obvious alternative
  • Whether exactly-once is broken down honestly, as at-least-once delivery plus idempotent processing, rather than claimed as a transport property
  • Whether data-integrity trade-offs are stated: what stays strictly consistent, where you accept delay, and how a mismatch is detected and corrected

How to prepare

  • Choose one project and write out its architecture, three decisions you made, the alternative each one beat, and what went wrong in production
  • Design the payments engine end to end: client idempotency keys backed by a unique constraint, a state machine for each payment, an append-only double-entry ledger, and a reconciliation job against bank files
  • Sketch key custody for signing: keys held in an HSM or managed KMS, a separate signing service, policy checks and approvals before signing, and an audit log of every signature request
  • Work through this guide's worked exercises on locating a billing reconciliation gap and on a resumable export that never skips a row

Behavioral Alignment

Candidates describe this round as an assessment of collaboration and fit in a remote-first environment. The reported behavioral prompts ask about a hard technical trade-off under a production deadline, disagreeing with an architectural decision made by a principal engineer or product manager, finding a security vulnerability or bottleneck and fixing it on your own initiative, and keeping code quality and test coverage high under continuous delivery. The question bank adds a pair-programming challenge under time pressure. A few stories you know deeply will hold up to follow-up questions better than one rehearsed answer per prompt.

What it evaluates

  • Whether a disagreement story shows evidence, a clear escalation path and a written outcome, not just persistence
  • Whether a deadline trade-off names the risk you accepted, who signed off, and when you paid it back
  • Whether your account of working across time zones and functions is concrete: what you wrote down, who you told, and how decisions were recorded

How to prepare

  • Map four projects to the reported prompts so each prompt already has a chosen story, and write each story three levels deep: what you did, why, and why not the alternative
  • Prepare one security or reliability story where you moved first, with the impact measured before and after
  • Prepare one story about remote collaboration: a decision made asynchronously across time zones, what you wrote down, and how disagreement was resolved in writing

Questions Circle candidates report

Data structures and algorithms

Ask which resource the problem actually takes away, because it is not always time. If the input does not fit in memory, the set you were going to build is gone and the bound that matters is space. If the data arrives once and cannot be revisited, anything needing a second pass is out whatever its complexity, and a constraint banning modification of the input removes the in-place trick that would otherwise be free. Naming the scarce resource first cuts the candidates down to a handful, and it stops you optimising a dimension the problem was never short of.

  • Implement an efficient in-memory cache with an eviction policy (e.g., LRU) that supports concurrent read and write operations safely.
  • Given a list of currency exchange rates, find the path that yields the maximum output for a starting currency, accounting for potential arbitrage loops.
  • Given an array of transactions, find the contiguous subarray with the maximum sum and return its value.
  • Implement a rate limiter to throttle API requests based on user IP addresses.

System design and architecture

At any point in a design you should be able to name the one resource that gives out first and what it costs to move it. Most follow-ups push on exactly that: traffic rises tenfold, a region is lost, a stricter consistency requirement appears. Answers hold up when the assumptions were said out loud early, because the design can then be re-derived from them rather than defended. The weak move under pressure is to add a component. The strong one is to say which part absorbs the change, which part has to be rebuilt, and roughly what the rebuild costs.

  • Explain how you would architect a secure system to manage cryptographic keys for signing blockchain transactions.
  • Design a highly available payments processing engine that guarantees exactly-once processing for transactions.

Behavioural and engineering judgement

Most engineering disagreements worth asking about are not settled by evidence, because both positions are defensible on what is known at the time: a schema shape, an abstraction one of you thinks is premature, fixing versus rewriting. The useful move is to say what would settle it, and what you do when nothing will. A common tiebreaker is which option is cheaper to undo, so you take the reversible one and set a date to look again. Weak answers argue who turned out to be right. Strong ones explain how the call got made while it was still undecidable.

  • How do you handle a situation where you disagree with an architectural decision made by a principal engineer or product manager?
  • Describe a time when you had to make a difficult technical trade-off to meet a tight production deadline.

Other topics

A large share of these are really questions about evidence: why something is slow, or how much of something you would need. Convert the prompt into a number or an observation. For diagnosis, name the measurement that would tell two candidate explanations apart and say what each predicts before you collect it. For sizing, carry the units through and stop at one significant figure. Listing everything that could plausibly be true is what the weak version looks like. What separates the strong one is a hypothesis, the observation that would falsify it, and what the arithmetic already rules out before any new data arrives.

  • Explain how you would implement robust error handling and transactional rollbacks in a multi-step Express/Node.js backend service.

Practice exercises with worked solutions

The prompts above tell you what gets asked.

Migrate a live partitioned event table without blocking ingest

usage_event is range-partitioned daily on ingested_at, holds roughly 250M rows per day across 400 live partitions, and is written at 10-40k rows/second. Two changes are required: quantity must move from double precision to numeric(20,6), and a new environment column must become NOT NULL with a default of 'production'. Ingest cannot stop. Give the ordered plan, naming for each step the lock it takes, what that lock blocks, and roughly how long it is held. Identify the one step that cannot be rolled back cleanly once traffic depends on it.

How a strong candidate works it

  • Classify the two changes before planning anything. Adding a column with a non-volatile default has been metadata-only since PostgreSQL 11, so it is cheap. Changing double precision to numeric is not binary-coercible, so alter column ... type rewrites every partition under ACCESS EXCLUSIVE and rebuilds its indexes; on this volume that is hours of blocked ingest and is simply not an option, which is why the plan is expand-and-contract rather than one statement.
  • Expand: add quantity_numeric numeric(20,6) and environment with its default on the parent. Both are catalogue-only but both take a brief ACCESS EXCLUSIVE that cascades to partitions, so run each with lock_timeout set to a second or two and retry on failure. A queued ACCESS EXCLUSIVE request blocks every reader behind it, which is how a metadata-only change turns into an outage.
  • Dual-write: deploy producer code that populates both columns on every insert, and leave it running before anything reads the new column. This is the step that cannot be reverted cleanly. Once readers depend on quantity_numeric, reverting the writer leaves rows with a null there, and the gap is only discoverable by re-reading the old column, which the readers have stopped doing.
  • Backfill older partitions in batches keyed on the primary key, oldest first, committing every few thousand rows with a pause between batches, and skipping the partition still receiving writes until it rotates. Each batch is an ordinary UPDATE taking row locks only. The cost is bloat and WAL rather than blocking, so watch dead tuples and let autovacuum keep pace instead of wrapping 400 partitions in one transaction.
  • Make NOT NULL cheap with the three-step form: add constraint ... check (environment is not null) not valid (brief ACCESS EXCLUSIVE, no scan), then validate constraint (SHARE UPDATE EXCLUSIVE, scans while reads and writes continue), then set not null, which from PostgreSQL 12 uses the validated check and skips its own full scan. Do this per partition, then on the parent.
  • Switch and contract: move reads to the new column behind a flag, verify over a full period that both columns agree on freshly written rows, drop the old column (metadata-only), and only then remove the dual-write. Any index on the new column goes on with CREATE INDEX CONCURRENTLY per partition, since CIC is not supported on a partitioned parent: create the parent index with ONLY, build each child concurrently, then ALTER INDEX ... ATTACH PARTITION until the parent index becomes valid.

Expected result (45 min): An ordered expand, dual-write, backfill, switch, contract plan with the lock named per step: metadata-only ADD COLUMN under a brief ACCESS EXCLUSIVE taken with lock_timeout; batched UPDATE backfill under row locks; CHECK NOT VALID, then VALIDATE under SHARE UPDATE EXCLUSIVE, then SET NOT NULL; CREATE INDEX CONCURRENTLY per partition with ATTACH PARTITION; and the dual-write-to-read switch identified as the step that cannot be reverted cleanly.

Check yourself

  • No step in the plan holds ACCESS EXCLUSIVE for longer than the configured lock_timeout.
  • The writer's error rate stays at zero through the rehearsal apart from the deliberate lock timeout.
  • After VALIDATE, SET NOT NULL completes in milliseconds on a table where the naive form scanned every row.
  • Freshly written rows agree on both columns for a full verification period before the old column is dropped.

Follow-ups

  • A CREATE INDEX CONCURRENTLY fails halfway through the partition list. What state is the table in, how do you detect it, and what do you run?
  • The producer computes quantity itself. What happens to a request already in flight when the dual-write deploy lands, and does it matter?
  • Give two queries that prove the backfill is complete: one cheap enough to run every minute, one authoritative.

Explain why the metering dashboard scans every daily partition

usage_event is range-partitioned daily on ingested_at and holds tenant_id, workspace_id, environment, sku, quantity numeric(20,6), occurred_at and ingested_at. The only relevant index is on (occurred_at). A dashboard runs select sku, sum(quantity) from usage_event where tenant_id = $1 and date_trunc('hour', occurred_at) >= $2 and environment = 'production' group by sku, and EXPLAIN shows a sequential scan of every partition. Give each distinct reason, rewrite the predicate so an index can serve it, propose the index, and state the write cost its column order adds.

How a strong candidate works it

  • Separate the three causes rather than blaming one. First, date_trunc('hour', occurred_at) wraps the column, so the predicate is not sargable against a btree on the bare column. Second, pruning keys off ingested_at while the query constrains occurred_at, so no partition can be excluded. Third, even made sargable, (occurred_at) is not tenant-leading, so for one tenant among thousands the scan reads the whole time range and discards nearly all of it.
  • Rewrite the bound carefully, because the obvious rewrite is only conditionally equivalent. date_trunc('hour', x) >= $2 equals x >= $2 only when $2 is already hour-aligned; for an arbitrary $2 it means x >= date_trunc('hour', $2) + interval '1 hour'. Normalise the parameter in the caller and leave the column bare.
  • Restore pruning with a second, redundant predicate on the partition key: ingested_at >= $2 - interval '<late-data horizon>'. State both sides of it. It prunes to a handful of partitions, and it silently omits any event whose ingest lagged past that horizon, which is precisely what a producer replay produces. Either document the horizon as a stated bound, or partition on occurred_at and move the problem into the dedup window instead.
  • Propose (tenant_id, occurred_at) include (sku, quantity) per partition. A partial index where environment = 'production' mostly saves size rather than selectivity, since production dominates the three environments; take it if non-production is a meaningful share and skip it otherwise.
  • Price the write path honestly. At roughly 250M rows/day each extra index is another insert plus WAL per row, and a tenant-leading key scatters inserts across one hot leaf per active tenant instead of appending to a single rightmost leaf, so page dirtying and random I/O both rise. An INCLUDE payload widens every leaf entry and enlarges the index accordingly.
  • Add the index-only-scan caveat before someone reports it as a regression: on a freshly appended table the visibility map is not yet set for recent pages, so the INCLUDE columns still cost heap fetches until autovacuum has been through, and the newest hour is exactly the data the dashboard reads.

Follow-ups

  • CREATE INDEX CONCURRENTLY is not supported on a partitioned parent. Give the sequence that gets this index onto 400 existing partitions without blocking ingest.
  • One tenant holds 200 times the median row count and the dashboard still times out for them with the index in place. What changes?
  • Should this read hit usage_rollup_hourly instead? State what that costs in freshness and what the watermark lets you promise.

Locate a billing reconciliation gap without rescanning ninety million events

A tenant's sealed invoice total is 0.4% below the sum of its raw usage_event rows for the period. That tenant has 90 million events over 30 days in a table partitioned daily on ingested_at, and its rollups carry source_max_ingested_at, revision and sealed_at. Recomputing all 30 days from raw is correct, and you are not going to do it. Give the procedure that locates the divergent (workspace, sku, hour) cell, the cost of each probe, and the one query you run before any of it.

How a strong candidate works it

  • Run the free query first. Sum raw quantity for the period restricted to ingested_at <= source_max_ingested_at of the sealed rollups, and compare that against the unrestricted sum. The rollup stores the watermark precisely so this can be answered without a scan. If the whole 0.4% sits above the watermark, nothing is broken: it is late data, it becomes an adjustment line, and the investigation ends in one query.
  • Only if the gap survives that test do you bisect, and you bisect by dimension rather than by rows. Compare 30 per-day totals, then inside the offending day compare the 6 SKUs, then the workspaces, then the 24 hours. That is roughly 30 + 6 + W + 24 grouped probes, each an indexed range scan over one daily partition for one tenant, against O(N) per attempt for the naive re-fold.
  • Quantify why naive is not merely slow but unusable mid-incident: at a generous 200,000 rows/second sequential, 90 million rows is about 7.5 minutes per attempt, you will want ten attempts, and every one competes for I/O on the same partitions live ingest is writing. The diagnostic worsens the backlog it is diagnosing.
  • Before fetching each comparison, state what it would look like under each hypothesis. Two adjacent hours off by equal and opposite amounts is occurred_at versus ingested_at bucketing. A whole day offset by exactly N hours is a timezone applied at the wrong layer. A gap confined to one SKU in one workspace is an environment filter. The same (tenant_id, idempotency_key) present in two ingested_day partitions is the dedup horizon losing a retry that crossed midnight.
  • Make the next bisection cheap by storing the aggregate you keep recomputing. A per-(tenant_id, ingested_day) count and quantity checksum turns step two from thirty probes into one read, and it is the same number the reconciliation job already produces.
  • Whatever you find, the sealed period does not change value. The correction is an adjustment line pointing at the line it reverses, carrying its own source_rollup_watermark, because the original invoice is the evidence of what the customer was charged.

Expected result (35 min): The watermark-bounded query accounts for the post-seal block entirely and removes it from the investigation. The ingested_at bucketing appears as adjacent hours off by equal and opposite amounts. The midnight-crossing duplicates appear as one (tenant_id, idempotency_key) present in two ingested_day partitions. Bisection reaches each cell in fewer than 70 probes.

Check yourself

  • The three injected defects sum to the original gap with no unexplained remainder.
  • Each probe's row count is bounded by one day-partition for one tenant, not by the period.
  • Removing the source_max_ingested_at bound from the first query makes a gap appear, and grow, while ingest runs, even after every defect is removed.

Follow-ups

  • The gap is 0.4% in one direction on one day and 0.4% the other way the next day. What does that shape rule in, and what does it rule out?
  • How do you distinguish a duplicate from a restatement, given revision and recomputed_at on the rollup?
  • Ingest is still running while you investigate. What makes your two numbers comparable at all?

A resumable usage export that never skips a row

Customers pull their own rows from usage_event through GET /v1/usage to reconcile against their own systems. The table is append-only and partitioned daily on ingested_at; a large tenant adds millions of rows a day while the export is being walked, and a client may pause for hours and resume. Specify the cursor, the index it requires, the tenant scoping, the ordering guarantee you can honestly offer, the per-page cost as the walk deepens, and what the client must do to avoid missing rows.

How a strong candidate works it

  • Rule out LIMIT/OFFSET on two independent grounds and say both, because fixing only one leaves the other. Correctness: rows inserted between page requests shift the window, so a walking client skips rows and repeats others, which for a reconciliation consumer is silent data loss rather than an error anyone sees. Cost: the database still produces and discards the skipped rows, so page N costs time proportional to N x page_size and a deep page degrades from milliseconds to seconds.
  • Use keyset pagination over a stable, unique, indexed ordering: WHERE tenant_id = $1 AND (ingested_at, event_id) > ($2, $3) ORDER BY ingested_at, event_id LIMIT $4, carrying the last row's pair as the cursor. The row-value comparison navigates a composite btree directly, so each page is O(log n + page_size) and stays constant as the walk deepens. The precondition is that the cursor columns never change value for a row, which ingested_at satisfies and updated_at would not.
  • Lead the index with tenant_id - (tenant_id, ingested_at, event_id) - which is simultaneously the correctness guard and the plan choice. An index on (ingested_at) alone forces a filter across every tenant's rows, and on a table where one tenant holds most of them that is fine only for that tenant and terrible for everyone else. Because ingested_at is also the partition key, a resumed cursor prunes to the partitions from the cursor forward.
  • Name the visibility hazard rather than assuming it away: in PostgreSQL now() is transaction start time, so a transaction that starts at T, inserts, and commits at T+8 s writes a row whose ingested_at is T but which becomes visible only at T+8 s. A walker that has already passed T never returns it. The gap equals the writer's longest transaction, so the mitigation is either a safety lag - serve only rows older than now() minus the longest permitted transaction - or a client that re-walks a trailing overlap window and deduplicates on event_id, which is stable and unique.
  • Offer the guarantee you can actually keep: ordering by (ingested_at, event_id) with no claim whatsoever about occurred_at order, and completeness only behind the safety lag or with the documented overlap-and-dedup obligation on the client. Saying this in the API reference is part of the design, because the client's reconciliation logic is what has to absorb it.

Expected result (20 min): Keyset pagination on (tenant_id, ingested_at, event_id) with a matching composite index, constant per-page cost and no sort; the guarantee limited to ingested_at ordering with nothing claimed about occurred_at; completeness delivered by a safety lag equal to the longest permitted write transaction, or by a documented client overlap window with deduplication on event_id.

Check yourself

  • The plan for a page should be an index scan with no Sort node. A Sort means the index column order does not match the ORDER BY and the constant-cost claim is false.
  • Under concurrent inserts the offset pager and the keyset pager return different sets. Count the difference rather than reasoning about it.
  • Your safety lag must be at least the longest transaction the writing service permits. If you cannot name that number, the completeness guarantee is not enforceable and should not be documented.

Follow-ups

  • The customer wants to reconcile by occurred_at instead of ingested_at. What breaks, and what would you offer them in its place?
  • One tenant starts a full-history export. How do you keep it from occupying every connection in the pool?
  • A customer reports a missing row. What do you check first, and what would each answer tell you?

Invoice detail latency triples after an ORM relationship refactor

An invoice detail endpoint returned in 40 ms at p99 last week. After a refactor replaced a hand-written join with ORM relationship access it returns in 1.4 s, and the regression grows with the number of invoice_line_item rows on the invoice. Database CPU rose, but no statement in the slow-query log exceeds 3 ms. You have request traces with per-span SQL, the ORM statement log, and a staging copy of the data. Produce an ordered diagnostic checklist, the measurement that confirms the cause before any code change, and the fix.

How a strong candidate works it

  • Count statements per request before reading any statement duration. A slow-query log hides this class by construction, because every individual query is fast and only their number is wrong; take one trace and count SQL spans.
  • Establish proportionality rather than asserting it: sample invoices with 5, 20, 60 and 200 line items and plot statements per request against line count. A straight line of slope 1 through an intercept of one or two identifies a lazy relationship load, and no index or cache would move that line.
  • Locate the emitting attribute access in the refactored code and check whether the same shape repeats one level deeper, for instance a tax or adjustment collection hanging off each line, which turns the cost quadratic.
  • Fix with a bounded statement count: either one join that fetches invoice and lines together, or two statements where the second is WHERE invoice_id = $1 AND tenant_id = $2. Keep tenant_id in the predicate so the read stays tenant-scoped even though invoice_id already implies it.
  • Choose between the two deliberately: the join duplicates the wide parent row across N children on the wire, the two-statement form avoids that for one extra round trip. Prefer the join for narrow parents and the split for wide ones.
  • Pin it with a per-request statement-count assertion in a test that varies line count, because a latency assertion passes on a small fixture and would not have caught this.

Follow-ups

  • The endpoint now also needs per-line tax rows. Show the shape that keeps statement count constant instead of reintroducing the same defect one level down.
  • How does this change if a transaction-pooling proxy sits between the service and the database, so each statement may land on a different backend session?
  • The same page paginates invoices with LIMIT and OFFSET. Why is that a second, independent defect, and what replaces it?

Argue against failing open when the control plane is unreachable

The gateway caches credential-to-context decisions with a sixty-second TTL. A design proposal says that when control-plane reads fail, pods should keep serving from expired entries indefinitely so a control-plane outage never becomes a product outage. You believe that converts every revocation into an unbounded one. Describe a design you argued against while it was still a live proposal: what you measured or modelled to make the case, what you conceded, who decided, and what happened afterwards. Say what would have changed your mind before the decision, not after it.

How a strong candidate works it

  • Reframe it from a values argument into a bounded-staleness argument. Both sides already accept the cache; the disagreement is only about the ceiling on how long a revoked credential keeps authorising. Put a number on the table — serve stale for up to fifteen minutes, then fail closed — and make the other side argue against a number rather than against a principle.
  • Bring arithmetic rather than adjectives: the rate of revocations with revoked_reason in ('suspected_leak','auth_version_bump'), the observed distribution of control-plane unavailability, and the product of the two, which is expected requests served by revoked credentials per outage-hour. At 30k requests/second the unbounded version is not a subtle exposure and the number says so.
  • Concede the strong half of the opposing case first, because that is what buys you the room: failing closed turns one service's outage into a total outage across three regions, and a control plane doing tens of writes per second is not engineered to the gateway's availability target. A proposal you have not steelmanned reads as reflex.
  • Propose the asymmetry that usually resolves this: stale entitlements cost bounded money (a quota fifteen minutes out of date over-serves by a computable amount), while a stale revocation costs unbounded access. Split the cached decision by what it authorises, give the two halves different staleness ceilings, and let the entitlement half fail open while the revocation half fails closed.
  • State the propagation dependency plainly, since it is the part that is missed: validity is also derived from the principal's auth_version, so password reset and sign-out-everywhere flow through this same cache. A design that bounds staleness for explicit revocation and not for auth_version bumps has only fixed half of it.
  • Say who decided, and what you did afterwards in either outcome: write the decision down with its number and a review date, and instrument the exposure you were worried about so the next round of the argument is settled by data instead of by seniority.

Follow-ups

  • Publish-subscribe invalidation is lossy under a partition, and a TTL is the only hard bound. What TTL do you pick, and what does it cost you at 30k requests/second?
  • The key was revoked because it was found in a public repository. Does your answer change, and where does that urgency live in the design?
  • You lost the argument and six weeks later the failure you predicted happens. What do you say in the review, and what do you not say?

Where candidates lose points

Applying the debit and the credit of a transfer as two independent updates in a banking simulation

In problems like the reported banking system with transfers, check everything before changing anything: both accounts exist, the source is not the destination (or self-transfers are explicitly allowed), and the balance covers the amount. Only then apply both sides together. Record each operation ID so a repeated call returns the original result instead of moving money twice. Test insufficient funds, unknown accounts, zero and negative amounts, and a replayed operation ID before you call the solution finished.

Returning expired keys from an in-memory store because expiry only happens in a background sweep

In-memory database design and TTL logic are among the most reported topics. Check expiry on every read, and treat a background sweep only as a way to reclaim memory. Decide the exact semantics at expiry (is a key with expiry time t readable at time t?) and write a test at that timestamp. When you add backup and restore, store either the absolute expiry time or the remaining TTL, and say which, because restoring at a later time changes the answer.

Claiming exactly-once processing in a payments design without saying where duplicates are removed

Several reported design questions (the payments engine, the ledger, bank reconciliation) depend on this one point. Say that delivery is at least once, and that exactly-once effects come from an idempotency key checked against a unique constraint in the same transaction that writes the ledger entry. Name what happens when a timeout leaves the outcome unknown: the client retries with the same key and gets the stored result. Finish with the reconciliation job that catches whatever the first two layers miss.

Breaking existing clients while adding filtering and pagination to an existing endpoint

The reported stack question asks for this change without breaking backward compatibility. Make every new parameter optional, keep the default response identical for requests that omit them, and add fields rather than renaming or removing any. If you introduce a cursor, keep accepting the old paging parameters. Write one test that sends a request exactly as an old client would and asserts the response has not changed.

Describing the team's project in the project deep-dive without separating your own decisions

When the conversation moves from what the system does to why it was built that way, a story told only about the team gives no evidence about you. Name the parts you designed or changed, the alternative you rejected for each and why, and one thing that failed in production and what you changed afterwards. If a decision was someone else's, say so, and explain how you would have argued it.

Algorithms first, for a candidate whose problem-solving has gone rusty

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 — Recruiter call and a map of the loop

  • Write your scope story for the recruiter conversation: the largest change you designed without review, what would have broken if you were wrong, and what you were on call for
  • Write questions for the recruiter: which level the loop targets, which stack the technical rounds use (Java, Go or MERN), and what the automated assessment and the project deep-dive cover
  • List the reported questions by category (algorithms, system design, stack-specific, behavioral) and mark the ones you could not start on today
  • Solve one easy stateful warm-up from the bank themes, such as resource operations keyed by formatted string IDs, with a harness that prints expected against actual

Deliverable: A one-page loop map listing six rounds, the questions for the recruiter, and your weak categories ranked.

Day 2 — Timed practice: stateful simulation problems from the question bank

  • From a blank file, build an in-memory key-value store with GET/SET/DELETE, TTL expiry checked on read, and backup/restore
  • Add a new requirement to the store, such as a prefix scan or a count of live keys, without restructuring the code, and note what made that easy or hard
  • Implement a banking system with deposits, transfers, idempotent operation IDs and a top-K query for outgoing volume
  • Test self-transfer, insufficient funds, unknown accounts, replayed operation IDs, and reads at the exact expiry timestamp

Deliverable: Two working simulations, each with a harness covering its boundary cases and a note on how each would take a further requirement.

Day 3 — Reported algorithm questions: graphs and arrays

  • Solve the maximum-sum contiguous subarray question over transactions, including the all-negative input, and state its complexity before you code
  • Detect a cycle in a directed graph of accounts and transfers using DFS with in-progress and finished states, then merge connected accounts with union-find
  • Solve the exchange-rate path question with -log weights and Bellman-Ford, and decide what to return when a cycle whose rate product exceeds 1 is reachable
  • Narrate each solution aloud in a plain editor without autocomplete

Deliverable: Four solved problems, each with its complexity, one edge case per problem that a careless version gets wrong, and one recorded narration.

Day 4 — Reported algorithm questions: caches, limiters and schedulers

  • Implement the reported in-memory LRU cache with a hash map and a doubly linked list, then make it safe under concurrent reads and writes and explain what the lock protects
  • Implement the reported per-IP rate limiter as a token bucket and as a sliding window, and explain how each cleans up idle IPs
  • Build a payment scheduler with cancel and a top-K outgoing query, from the bank themes, choosing structures from the operations it has to support
  • Return the N most frequent event keys with a bounded heap, and state when a full sort would be simpler

Deliverable: Four implementations with tests, and a short note per problem on the concurrency or memory trade-off you chose.

Day 5 — Technical rounds: your primary stack

  • Explain your stack's concurrency model in writing: the Node event loop and what blocks it, Go goroutines and channels, or Java threads and locks
  • Add filtering and cursor pagination to an existing list endpoint without breaking old requests, then compare your design with the keyset approach in this guide's worked exercise on a resumable export that never skips a row
  • Answer the reported question on error handling and transactional rollback in a multi-step Express service by implementing it, forcing a failure at each step, and confirming no partial state survives
  • Diagnose an N+1 query regression using this guide's practice question on invoice detail latency after an ORM relationship refactor, and write the statement-count test that pins the fix

Deliverable: A modified service with backward-compatible pagination, a rollback test suite, and a written explanation of your stack's runtime model.

Day 6 — Project deep-dive and payments system design

  • Design the reported payments engine with exactly-once processing: idempotency keys, a payment state machine, an append-only double-entry ledger, and reconciliation against bank records
  • Design signing-key custody for blockchain transactions, as in the reported key-management question: an HSM or KMS, a separate signing service, approval policy, and an audit trail
  • Work through this guide's worked exercises on locating a billing reconciliation gap and on migrating a live partitioned event table without blocking ingest, and write down one idea from each you would reuse
  • Prepare your project story: architecture, three decisions and the alternatives they beat, and one production failure

Deliverable: Two design sketches naming their failure modes and recovery paths, plus a project story you can take three levels deep.

Day 7 — Behavioral alignment and a full rehearsal

  • Map four projects to the reported prompts: a deadline trade-off, disagreeing with an architectural decision, a security vulnerability or bottleneck you fixed, and keeping test coverage high under continuous delivery
  • Prepare the pair-programming-under-time-pressure story from the bank themes, including what you said out loud when you were stuck
  • For each reported behavioral prompt, have someone ask 'why' three times on your chosen story and note where your answer starts repeating itself
  • Run a mock with a friend: one live coding problem narrated aloud, one design question, and three behavioral prompts with repeated follow-up questions

Deliverable: A story index covering every reported behavioral prompt, and notes from the mock listing the two weakest moments to fix.