Revolut System Design interview

Revolut System Design interview

This blog breaks down how to ace Revolut’s System Design interview by designing around a correctness-first ledger, safe retries, FX and card workflows, and reconciliation when external payment truth arrives late.

14 mins read
Jan 16, 2026
Share
editor-page-cover

Revolut builds fintech systems where “it worked in prod” is meaningless unless the money is correct, the audit trail is defensible, and the business can reconcile against external rails weeks later. The system design interview is designed to see whether you can reason about financial invariants under distributed failure, not just scale an API.

In a Revolut interview, you’re not only designing a service. You’re designing a financial machine: a ledger as source of truth, derived projections for speed, risk gates that can stop or delay money movement, and operational controls that keep humans and regulators confident when the network lies to you.

Cover
Grokking Modern System Design Interview

System Design Interviews decide your level and compensation at top tech companies. To succeed, you must design scalable systems, justify trade-offs, and explain decisions under time pressure. Most candidates struggle because they lack a repeatable method. Built by FAANG engineers, this is the definitive System Design Interview course. You will master distributed systems building blocks: databases, caches, load balancers, messaging, microservices, sharding, replication, and consistency, and learn the patterns behind web-scale architectures. Using the RESHADED framework, you will translate open-ended system design problems into precise requirements, explicit constraints, and success metrics, then design modular, reliable solutions. Full Mock Interview practice builds fluency and timing. By the end, you will discuss architectures with Staff-level clarity, tackle unseen questions with confidence, and stand out in System Design Interviews at leading companies.

26hrs
Intermediate
5 Playgrounds
26 Quizzes

Revolut-style system design is about proving you can keep correctness under retries, partitions, and late-arriving external truth—without losing throughput or auditability.

What Revolut optimizes for

What interviewers probe

What strong candidates do

Financial correctness at scale

Ledger modeling, invariants, reversals

Put the ledger at the center and make everything else derived

Low-latency card + payments

Auth/capture timing, concurrency

Separate “decision path” from “posting path” while keeping idempotency

Compliance + auditability

Immutable trails, retention, access controls

Model append-only events, correction entries, and long-lived traceability

Global + multi-region

Data residency, failure isolation

Explicit region boundaries and “can this fail independently?” reasoning

Summary (after the explanation):

  • Treat the ledger as the source of truth, not “a database table.”

  • Show your invariants before you show your microservices.

  • Make failure behavior a first-class design output, not a footnote.

Why Revolut’s system design questions feel different#

Most companies reward you for designing a service that’s fast and scalable. Revolut rewards you for designing a system that stays financially correct when the real world misbehaves: partners time out, card networks deliver duplicates, FX rates move mid-flow, and compliance requires you to explain every cent.

A typical Revolut prompt might sound like “design multi-currency ledger + card payments,” but the real test is whether you naturally reach for the constraints Revolut lives with daily: idempotency boundaries across rails, audit trails you can defend years later, and reconciliation when external settlement is delayed or disputed.

Interviewers aren’t looking for the fanciest architecture. They’re listening for whether you treat money movement like a state machine backed by an append-only ledger, with explicit correctness rules.

“Eventual consistency is fine”

Some flows demand strong posting semantics

“Strong consistency for ledger writes, derived reads can be eventual”

“Retries are harmless”

Retries can double-charge and create disputes

“Idempotency keys at every boundary, plus dedupe in the ledger”

“Partner response is truth”

Partner truth arrives late and can change

“Operational state now, financial truth later via reconciliation”

Summary (after the explanation):

  • Revolut questions are “finance-first distributed systems.”

  • Treat partners as unreliable and late.

  • Make correctness legible: invariants, state transitions, and audits.

Step one: Clarify requirements like a financial engineer#

Start by making the interview feel like a production design review. You want to surface requirements that change the architecture: what must be strongly consistent, what can be derived, what is reversible, what requires a human review, and what must be retained for years.

Instead of asking generic questions, tie your questions to Revolut workflows: “Is this a card authorization (temporary hold) or a capture (posting)?” “Is FX rate locked at quote time or at posting time?” “Do we need data residency boundaries per regulatory zone?” “What’s the allowed reconciliation lag with external settlement files?”

A strong opening is not a list of questions. It’s a short story: “If we time out after we’ve posted to the ledger but before we respond, how do we prevent a second debit?”

Correctness

“What are the non-negotiable invariants?”

Determines ledger rules, atomicity, and reversal strategy

Latency

“Auth path budget: 100–300 ms globally?”

Forces caching, regional routing, and precomputed risk features

Risk

“Do we block, step-up, or allow then review?”

Shapes risk gates and asynchronous workflows

Compliance

“Retention: seven years? WORM storage?”

Drives immutable logs, access control, and storage tiering

Geography

“Data residency by legal entity/region?”

Implies multi-region isolation and controlled replication

Summary (after the explanation):

  • Ask questions that change data consistency, posting, and reversibility.

  • Tie requirements to auth/capture/FX/risk/compliance.

  • Make reconciliation lag and partner unreliability explicit early.

Step two: Identify the core Revolut workflows#

After requirements, anchor the system around a few high-value workflows that Revolut systems repeatedly implement: multi-currency balance management, card authorization/capture, FX quoting and conversion, fraud/risk decisions, and compliance-grade auditability. You’re proving you understand what moves money and what can stop money from moving.

A good way to structure this is to pick one “happy path” and then immediately show how it fails safely. For example: card authorization is a low-latency decision that may place a hold; capture posts a final ledger entry later; settlement arrives later still and can disagree; disputes introduce reversals and chargebacks. Each step touches different systems and has different correctness requirements.

Revolut interview language you should use naturally: “ledger is source of truth,” “derived projections,” “risk gate,” “idempotency boundary,” “audit trail,” “multi-region failure isolation,” “data residency.”

Card authorization

Approve/decline in milliseconds

Create/adjust a hold

Timeouts, duplicate auths, partial approvals

Capture/clearing

Finalize amount and merchant

Post final debit, release hold

Late captures, incremental captures, mismatches

FX quote + exchange

Lock rate and fees

Post two legs + fees

Volatility, quote expiry, slippage handling

Bank transfer

Route + compliance checks

Pending → posted on confirmation

Delayed confirmations, recalls, rejects

Fraud/risk

Block/step-up/review

Possibly freeze funds or hold

Model outages, false positives, audit demands

Summary (after the explanation):

  • Show one end-to-end flow, then show where reality breaks it.

  • Separate operational steps from financial posting.

  • Always connect workflow steps back to ledger entries and reversals.

Step three: Define a robust and auditable data model#

Your data model is where Revolut interviews are won or lost. The interviewer wants to see that you don’t model balances as “a number in an accounts table.” Revolut-grade systems treat balances as derived from append-only entries, with clear invariants and traceability.

Start with the ledger primitives: accounts (often multi-currency sub-accounts), journal entries, postings (debits/credits), and references that tie every posting back to an originating intent (card auth, capture, transfer, fee, refund). From there, you build derived projections: available balance, booked balance, per-merchant spend, risk features, statements, and compliance reports.

If you can’t reconstruct a user’s financial history from immutable events, you don’t have a financial system—you have an API with a liability problem.

Account (per currency)

A ledger bucket with an owner and currency

Prevents cross-currency confusion and supports residency rules

Journal entry

A single financial “fact”

Append-only evidence for audits and disputes

Posting

Debit/credit lines within a journal entry

Enables double-entry balancing and invariants

Transaction reference

External + internal identifiers

Links retries, partner messages, and reconciliation

FX rate snapshot

Rate + spread + timestamp + source

Explains why two legs posted the way they did

Summary (after the explanation):

  • Balances should be derived; the ledger is the source of truth.

  • Every posting must be traceable to an intent and external reference.

  • Model FX rates as snapshots, not “current rate at read time.”

Double-entry ledger modeling and invariants Revolut cares about#

Double-entry isn’t a buzzword in these interviews—it’s your correctness framework. Revolut systems often support multi-currency, holds, fees, refunds, chargebacks, and cross-entity movements. Double-entry gives you a way to prove that money doesn’t magically appear or disappear, even when events arrive out of order.

In practice, you’ll model each financial “fact” as a journal entry with postings. A card authorization hold might move funds from “available” to “held” (internal accounts), while a capture moves from “held” to “settled expense,” possibly with a separate fee posting. A refund reverses a prior capture, and a chargeback might post to a dispute suspense account pending investigation. The key is that every movement has a counter-movement.

What interviewers listen for on correctness: you don’t just say “double-entry,” you state invariants and show how you enforce them in code and data constraints.

Every journal entry balances (sum debits = sum credits) per currency

Transactional write + validation in posting service

“Money creation/destruction,” unreconcilable books

Immutability of posted entries

Append-only storage, correction entries

Audit trail becomes legally weak; disputes become unprovable

Idempotent posting per business intent

Unique constraint on (ledger_account, business_ref, sequence)

Duplicate debits/credits under retries

Holds cannot exceed available funds

Atomic check-and-post with isolation

Negative available balances; overspend during concurrency

FX legs use the same rate snapshot

Rate locked in intent and stored with entry

“Phantom FX,” user-visible mismatch, P&L drift

Clear separation of operational vs financial state

Operational state machine + ledger as final fact

Systems disagree, reconciliation impossible

Summary (after the explanation):

  • Double-entry is your proof system; invariants are your contract.

  • Enforce invariants at write time, not by offline repair.

  • Prefer correction entries over overwrites for defensible audits.

Step four: Propose a secure, scalable Revolut-style architecture#

A strong architecture answer starts with the ledger write path and builds outward. You want to show a “thin” decision path for real-time latency (especially cards) and a “thick” posting and audit path for correctness. Revolut systems usually split transactional writes from read-optimized projections and analytics.

A common pattern is: API gateway → domain service (cards/transfers/FX) → risk gate → intent creation → ledger posting → event emission → derived projections. Partners and rails integrate through dedicated adapters, and every external message is treated as untrusted input that must pass idempotency and validation.

Say this out loud: “The ledger is the source of truth. Everything else is a projection or an integration boundary.”

Ingress/API

AuthN, device signals, rate limits

“Idempotency at API boundary; don’t trust clients”

Domain service

Build intents + state machine

“Operational state is not financial truth”

Risk gate

Fraud scoring + policy checks

“Fail closed for high-risk, degrade gracefully for low-risk”

Ledger service

Validate invariants + post entries

“Strong consistency, append-only, unique constraints”

Event bus

Fanout for projections and audit

“Exactly-once is hard; design idempotent consumers”

Projections

Available balance, statements, alerts

“Derived, replayable, region-scoped”

Audit/compliance

Immutable logs + access controls

“WORM storage, segregation of duties, long retention”

Rail adapters

Card networks, bank rails, webhooks

“Retries, dedupe, and late confirmations are normal”

Summary (after the explanation):

  • Build from the ledger outward; keep projections derived.

  • Put risk gates before posting where possible, but plan for async review.

  • Treat rails as unreliable and design for late truth.

Step five: Address real-world fintech complexity with failure-mode reasoning#

This is where you move from “design” to “engineering.” Revolut interviewers often ask “what happens when…” because the system’s behavior under failure is the real test. You should proactively walk through a few failure stories and narrate the system’s decisions, invariants, and recovery paths.

Your goal is not to list edge cases. It’s to show that you can keep the ledger correct while still operating: retries should not double-post, FX should not create mismatched legs, and partner delays should not corrupt state. You also want to show operational controls: observability, alerts, manual tooling, and reconciliation workflows.

The best candidates don’t promise “no failures.” They promise “no silent financial corruption,” and they show the mechanisms.

Duplicate submissions

Client retries, network timeouts

Idempotency keys + ledger dedupe constraints

Partial failures

Posted but response lost

Return by idempotency lookup; never “try again blindly”

FX volatility

Rate changes between quote and post

Rate snapshot + expiry + slippage policy

Partner delays

Clearing/settlement late

Operational state pending; financial truth via reconciliation

Risk service outage

Model store down

Degrade with rules, or fail closed by segment

Multi-region incident

Partition or region outage

Failure isolation + region-scoped ledgers + controlled recovery

Summary (after the explanation):

  • Explain failure behavior as a walkthrough, not a list.

  • Make “no silent corruption” your north star.

  • Always tie recovery back to idempotency, invariants, and reconciliation.

Failure-story walkthrough 1: Duplicate submissions plus partial failures#

Imagine a user taps “Exchange 500 EUR to USD” and the app sends POST /fx/execute. The request reaches the API, passes authentication, and the FX domain service creates an intent with an idempotency key (from the client) and a server-generated business reference. It then posts the double-entry journal entry: debit EUR account, credit USD account, plus a fee posting.

Now the network blips right after the ledger commit but before the API returns. The client retries. Without a rigorous design, you risk a second posting and a duplicated exchange. In a Revolut-grade design, the retry hits the API and is mapped to the same idempotency record. The system does not “run the exchange again.” It fetches the already-created intent outcome (and ledger reference) and returns the same result deterministically.

What you want to say in the interview: “If the ledger commit succeeded, retries must return the same outcome without re-posting. That’s an idempotency boundary backed by unique constraints.”

1

Client sends execute with idempotency key

Client retries become detectable

2

Server stores intent keyed by (user, key)

“Intent is unique”

3

Ledger posts with unique business reference

Dedupe enforced at source of truth

4

Response lost

Safe because commit is durable

5

Client retries

API returns by looking up intent outcome

Summary (after the explanation):

  • Persist intent before posting and keep it idempotent.

  • Enforce dedupe at the ledger, not only at the API.

  • Retries should read results, not re-run side effects.

Failure-story walkthrough 2: FX rate volatility mid-transaction#

Consider a card purchase in USD for a customer whose base balance is in EUR. The authorization arrives at time T with amount USD 100. You may need to reserve EUR equivalent funds. But FX is volatile; the rate at T might differ from the rate at capture time (hours or days later), and capture can be for a different amount.

A strong design makes the policy explicit. One approach: lock an FX rate snapshot at authorization for the hold, then at capture post the final amount using either the original snapshot (if within policy) or a new snapshot with a transparent adjustment entry. Another approach: hold in the transaction currency if possible and do conversion at capture/settlement. Either way, the key is: you never “look up the current rate” at random points. You bind postings to a stored snapshot so the ledger can explain itself.

Interview language that lands well: “FX conversion is a two-leg posting using a stored rate snapshot. If rate changes, we post adjustments explicitly rather than hiding it in a recalculated balance.”

Lock rate at auth

Low volatility or short window

Hold uses snapshot

Predictable, but needs expiry logic

Convert at capture

Auth is just a hold

Final posting uses snapshot at capture

More accurate but less predictable

Adjustment entry

Capture differs from hold

Post delta as separate entry

Auditable explanation of slippage

Summary (after the explanation):

  • Bind postings to an FX rate snapshot.

  • If amounts change, use explicit adjustment entries.

  • Don’t let “current FX rate at read time” leak into correctness.

Failure-story walkthrough 3: Partner delays and late confirmations#

Card networks and bank rails don’t always confirm quickly. You might authorize successfully, then wait a long time for capture/clearing. For transfers, you might submit to a partner and get an ACK, but final confirmation arrives later via batch files or delayed webhooks. If you treat “ACK received” as “money moved,” you’ll create a reconciliation nightmare.

A Revolut-ready design splits operational state from financial state. Operational state tracks what you attempted and what partners told you so far. Financial state is what the ledger has posted as final facts. Your system can show “pending” in the UI while the ledger holds funds, and later reconcile to settlement truth. If the partner sends a late rejection, you don’t edit history—you post a reversal or correction entry.

A reliable mental model: “External truth arrives late. The ledger records our best-known facts now, plus the mechanisms to correct later.”

Submit transfer

SUBMITTED

Optional pending/suspense posting

“Pending”

Partner ACK

ACCEPTED

No final posting yet

Still pending

Settlement file arrives

SETTLED/REJECTED

Final post or reversal

Moves to “Completed” or “Failed”

Summary (after the explanation):

  • Model operational state separately from final ledger facts.

  • Expect late truth and design explicit corrections.

  • Keep UI state honest: “pending” is a feature, not a bug.

Idempotency, retries, and deduplication across external payment rails#

In fintech, retries are not a corner case. They’re the default behavior of clients, networks, and partners. Your job is to define idempotency boundaries and enforce them consistently from the API down to the ledger and out to partner webhooks.

A good Revolut answer states where idempotency is guaranteed, where it cannot be guaranteed, and how you contain damage. For example, you can guarantee idempotent ledger posting for a given business reference, but you cannot guarantee a card network won’t send a duplicate notification. So you make consumers idempotent and ensure that external callbacks map to the same internal reference.

If you say “we’ll make it exactly-once,” expect pushback. If you say “we design idempotency at each boundary and make consumers dedupe-safe,” you’ll sound senior.

A retry-storm scenario (step-by-step)#

A “retry storm” is when one slow dependency triggers exponential retries across layers.

  1. A card network sends authorization requests. Your edge is healthy, but the risk service is slow due to a feature store issue. Auth processing time creeps up.

  2. The card network times out (their timeout is often shorter than you’d like). They retry the same auth with the same network identifiers.

  3. Meanwhile, your API layer has also retried the internal call to the risk gate. Now you have duplicates within your system and duplicates from outside.

  4. Some requests reach ledger posting, some time out before response, and some get declined due to risk gate fallback. Without dedupe, you might place multiple holds.

  5. The storm continues until you apply backpressure, tighten timeouts, and force deterministic behavior: “same request → same outcome.”

What to emphasize: “We cap retries, apply exponential backoff, and shift to ‘lookup-by-idempotency’ rather than ‘re-execute’ once an intent exists.”

Client → API

Idempotency-Key

+ user scope

API idempotency store

Dedup client retries

API → domain intent

Server-generated business_ref

Intent store

Links all downstream effects

Domain → ledger

business_ref

+ sequence

Ledger unique constraint

Prevent double-posting

Domain → partner

partner_request_id

Partner adapter store

Safe resubmission

Partner → webhooks

partner_event_id

Webhook inbox/dedupe table

Idempotent consumption

Event bus consumers

(event_id, consumer)

Consumer offset store

Prevent double-apply in projections

Summary (after the explanation):

  • Define idempotency keys per boundary; don’t reuse one key everywhere blindly.

  • Persist intent early and make retries resolve by lookup.

  • Make every consumer dedupe-safe because duplicates are inevitable.

Reconciliation, settlements, and disputes#

Reconciliation is where “distributed systems” meets “accounting reality.” External rails often finalize financial truth later: card settlement files, bank transfer confirmations, partner adjustments, and chargebacks. The interviewer wants to see that you can build a system that behaves correctly when the final truth arrives late and sometimes contradicts earlier assumptions.

You reconcile by treating the ledger as your source of truth and external rails as external truth sources that must be mapped to your ledger references. The mapping itself is a first-class problem: you need stable identifiers, inboxing of external messages, and deterministic matching rules. When mismatches appear, you create adjustment entries and route exceptions to ops tooling.

“Reconciliation” is not a cron job. It’s a continuous correctness loop with deterministic matching and explicit exception handling."

Authorization

Real-time network message

Fast path

Place hold, store network refs

Capture/Clearing

Merchant submits later

Merchant delay

Post capture, release/adjust hold

Settlement

Batch files/clearing reports

Network batching

Confirm final amounts and fees

Refund

Merchant-initiated later

Business process

Post refund and link to original

Chargeback

Dispute workflow

Card scheme timelines

Move to dispute accounts, investigate, post outcome

Operational state vs financial state (the table interviewers love)#

A strong candidate explicitly separates “what we think is happening” from “what is financially final.”

Auth

APPROVED/DECLINED

Hold posted or not posted

Capture

CAPTURE_REQUESTED/CONFIRMED

Final debit posted + hold released

Settlement

SETTLEMENT_PENDING/RECEIVED

Settlement-confirmed entry or adjustment

Refund

REFUND_PENDING/COMPLETED

Credit posted linked to original

Chargeback

DISPUTE_OPEN/WON/LOST

Dispute postings + final resolution entries

The phrase to use: “Operational state drives user experience; the ledger drives financial truth and auditability.”

Summary (after the explanation):

  • External truth arrives late; design deterministic matching and adjustments.

  • Keep operational state and financial state separate.

  • Build exception paths: disputes, mismatches, and manual review tooling.

Example prompt: Design a real-time multi-currency ledger for Revolut accounts#

Start by narrating the happy path: a user holds EUR and USD balances, wants to exchange and then spend on a card. Your architecture should show the ledger at the center, projections for reads, and risk gates for decisions.

Then “show” the core flows. An FX execute creates an intent, stores an FX rate snapshot, and posts a journal entry with two legs and fees. A card authorization checks available funds (derived projection), runs risk, and posts a hold entry. Capture posts final entries later and triggers reconciliation when settlement arrives.

If you only talk about services, you’ll sound generic. If you talk about posting rules, idempotency, and reconciliation, you’ll sound like a Revolut engineer.

Intent service

Creates immutable intent with references

Retries resolve by lookup

Ledger posting service

Validates and posts journal entries

Double-entry invariants + idempotency

Balance projection

Materialized available/held/booked

Rebuildable from events

FX rate service

Provides quote + snapshot

Snapshot stored with postings

Risk gate

Fraud + policy checks

Deterministic outcomes under retry

Reconciliation service

Matches external truth to internal refs

Adjustments are explicit entries

Summary (after the explanation):

  • Model intents, then post to the ledger, then project.

  • Keep FX snapshots attached to postings.

  • Reconciliation and disputes are part of the core, not “later.”

What a strong candidate answer sounds like#

You want your answer to sound like you’ve operated systems like this: crisp invariants, explicit boundaries, and specific failure behavior. Avoid vague claims (“we’ll ensure consistency”) and replace them with enforceable mechanisms (“unique constraint on business_ref prevents double-posting”).

“I’ll treat the ledger as the source of truth with append-only journal entries and double-entry validation. All reads come from derived projections that can be rebuilt. I’ll define idempotency boundaries at the API, intent, ledger, and partner layers, and I’ll model operational state separately from financial state because settlement arrives late. Failures should never cause silent financial corruption; they should produce deterministic outcomes or explicit correction entries with audit trails.”

Did I state invariants before architecture?

Correctness-first thinking

Did I define idempotency boundaries and keys?

Real-world resilience

Did I separate ledger writes from projections?

Scalability without sacrificing truth

Did I cover auth/capture/settlement timing?

Payments domain maturity

Did I explain reconciliation and disputes?

Operational reality awareness

Summary (after the explanation):

  • Sound deterministic: invariants, constraints, and state machines.

  • Make failures explicit and survivable.

  • Tie everything back to auditability and external reconciliation.

Final thoughts#

If you want to do well in the Revolut System Design interview, aim to be the engineer who makes the system provably correct under messy conditions: retries, duplicates, FX volatility, and late settlement truth. Put the ledger at the center, speak in invariants, and show how your design behaves when the network and partners misbehave.

The best answers feel like a production design doc narrated aloud: clear posting rules, explicit idempotency, derived projections for speed, risk gates for safety, and reconciliation pipelines for reality. If you can tell that story with confidence—and with concrete failure walk-throughs—you’ll stand out.

Happy learning!


Written By:
Zarish Khalid