Design a Banking System on AWS
A worked system-design example for a core banking/ledger system on AWS — the strict consistency, auditability, and compliance requirements that push this design toward RDS transactions, double-entry ledgers, and heavy encryption/audit logging.
Want a visual for this topic?
Generate a diagram tailored to Design a Banking System on AWS — the AI picks whichever visual (architecture, flowchart, ER diagram, etc.) best fits this specific AWS concept.
Sign in to generate a visual →🎓 Learning objectives
- •Explain why a banking ledger needs strict ACID transactional guarantees, unlike most other system-design examples in this course
- •Design a double-entry ledger schema and explain why it's the standard approach
- •Identify every AWS service needed to meet typical financial compliance/audit requirements
- •Explain idempotency's critical role in preventing duplicate financial transactions
What is it?
A banking system's core AWS design centers on strict ACID transactional guarantees for the ledger (typically via RDS/Aurora, using a double-entry accounting schema where every transaction is recorded as balanced debit/credit pairs rather than a single mutable balance), mandatory idempotency keys on every financial operation to prevent duplicate transactions on retry, and a heavy compliance/audit layer (CloudTrail, KMS encryption, GuardDuty/Security Hub, strict IAM/VPC network isolation) reflecting the regulatory requirements financial systems are held to.
Why it exists
This design's emphasis on strict consistency, immutability, idempotency, and heavy compliance tooling exists because financial systems operate under legal and regulatory requirements (accurate record-keeping, auditability, fraud prevention) that most other software domains don't face in the same way — a bug that causes a customer's product recommendation to be slightly wrong is an inconvenience; a bug that causes a duplicate financial transaction or an unbalanced ledger is a regulatory incident.
Problem it solves
It solves building a financial ledger system that guarantees transactional correctness (no lost, duplicated, or partially-applied transactions), provides a complete, self-verifying, immutable audit trail, and meets the encryption, access-control, and continuous-monitoring requirements financial regulatory frameworks typically demand.
Intuition
The single defining difference between this design and almost every other system-design example: correctness and auditability are non-negotiable, override every other design consideration (speed, cost, simplicity), and this shapes essentially every choice — the ledger schema, the database type, the idempotency requirement, and the entire security/compliance layer wrapped around it.
Analogy
Most of the system designs in this course are like a busy retail floor where a little bit of staleness or an occasional retry is a minor inconvenience. A banking ledger is like a bank vault's official paper ledger book from a century ago — every single entry must be exact, permanent, cross-checked against a matching opposite entry, and available for an auditor to trace years later; there is zero tolerance for 'probably correct' here.
Technical explanation
A double-entry ledger table typically stores each entry as its own immutable row (transactionId, accountId, amount, entryType (debit/credit), timestamp), with application logic (enforced by a database constraint or a checking process) guaranteeing that every transaction's set of entries sums to exactly zero — this structural invariant is what makes the ledger 'self-auditing,' since any bug that violates it is immediately and mechanically detectable by summing entries, rather than requiring external reconciliation to notice. Idempotency-key enforcement typically uses a unique database constraint on the key column within the same table/transaction as the actual ledger insert, so the uniqueness check and the insert happen atomically — checking for existence in a separate prior query (rather than relying on the constraint itself to reject a duplicate) introduces a race condition window where two near-simultaneous requests with the same idempotency key could both pass the check before either has inserted.
Architecture
The core ledger runs on RDS/Aurora within private subnets of a tightly-segmented VPC, accessible only from application-tier compute via security groups scoped to exactly the needed access, with no direct internet exposure at any layer touching the ledger. Every financial operation is processed as a single database transaction inserting balanced debit/credit entries, gated by an idempotency-key uniqueness check performed within the same transaction to avoid a race condition between the check and the insert. CloudTrail logs every API call across the account (management and, for the ledger's own data access patterns, potentially S3/RDS data events too) to an immutable, access-restricted log destination; KMS (with customer-managed keys where regulatory requirements call for it) encrypts data at rest; GuardDuty and Security Hub run continuously for threat detection and compliance-standard scoring (PCI DSS, and often additional financial-specific frameworks).
Workflow
- Client submits a financial transaction request with a client-generated idempotency key. 2) The system checks whether that idempotency key has already been processed (a unique constraint check) — if so, returns the original result without reprocessing. 3) If new, the system executes a single database transaction that inserts balanced debit and credit ledger entries (and updates any cached current-balance summary) atomically — committing only if both entries succeed, rolling back entirely on any failure. 4) The transaction and all administrative/API actions are logged immutably via CloudTrail for audit purposes. 5) Data at rest is encrypted via KMS (ideally customer-managed keys with defined rotation policies), and the core ledger database sits in a tightly network-isolated VPC with no direct internet exposure, accessed only through tightly-scoped IAM roles.
Example
A customer initiates a $500 transfer between two accounts; the request includes a client-generated idempotency key, checked against a unique constraint before processing; the actual transfer executes as a single RDS transaction inserting a balanced debit entry (-$500 from account A) and credit entry (+$500 to account B) into an immutable ledger table, committing atomically — if any part fails, the entire transaction rolls back, leaving no partial state; every step is logged via CloudTrail and the data is encrypted at rest via a customer-managed KMS key.
Real-world usage
Every real banking and financial-services platform is built around some form of double-entry ledger with strict transactional guarantees, mandatory idempotency on financial operations, and heavy compliance/audit tooling — this is one of the most frequently asked 'high-stakes correctness' system-design interview questions specifically because it tests whether a candidate understands that not every system should default to the eventually-consistent, horizontally-scaled patterns appropriate elsewhere, and can correctly identify when strict consistency is genuinely non-negotiable.
Trade-offs
A double-entry ledger with immutable entries is more complex to implement and query than a simple mutable balance field, but is the only design that provides both self-verifying correctness (the books must balance) and a complete audit trail — a tradeoff financial systems accept without question given regulatory requirements. Using RDS/Aurora for the core ledger trades some of DynamoDB's horizontal scalability for the relational integrity, complex transaction, and ad-hoc query capabilities a ledger and its auditors genuinely need — an explicit, deliberate choice for this specific component, not a default recommendation for every part of a banking platform's broader system.
Visual explanation
Picture an old-fashioned bank vault ledger book, written in ink, where every single page entry has a matching opposite entry on another page, and nothing is ever erased — only new correcting entries are added, always in balanced pairs, with a security guard (CloudTrail) logging every single person who so much as opened the book, and every page individually locked in a vault (KMS encryption) that only specific, badge-checked staff (IAM roles) can access at all.
Advantages
- —
A double-entry ledger is inherently self-auditing — any bug that unbalances the books is immediately detectable by checking that debits equal credits, rather than requiring external verification
- —
RDS/Aurora's ACID transactions provide the strict atomicity guarantee needed for a debit-and-credit operation to never leave the system in a partially-applied state
- —
Idempotency keys eliminate an entire class of duplicate-transaction bugs caused by client retries after network timeouts
- —
CloudTrail plus KMS plus GuardDuty/Security Hub together provide the audit trail, encryption, and continuous threat-detection posture that financial compliance frameworks typically require
Disadvantages
- —
The strict consistency and heavy compliance tooling this design requires add real latency, cost, and operational complexity compared to the more relaxed, eventually-consistent designs appropriate for other domains covered elsewhere in this course
- —
A double-entry, immutable-ledger schema is more complex to query for simple 'what's my current balance' use cases than a single mutable balance field, typically requiring a maintained current-balance summary/materialized view alongside the immutable entry log
- —
RDS/Aurora's vertical-scaling-oriented model requires more careful capacity planning for very high transaction volumes than DynamoDB's more automatic horizontal scaling, a genuine tradeoff against the relational integrity this design specifically needs
- —
The compliance/audit layer (CloudTrail, KMS key management, GuardDuty/Security Hub monitoring, strict IAM) is itself an ongoing operational responsibility requiring dedicated security/compliance expertise, not a one-time setup
Common mistakes
- —
Modeling account balances as a single mutable field updated in place, instead of an immutable double-entry ledger, losing both the self-auditing property and the complete transaction history a real financial system and its auditors need
- —
Not requiring idempotency keys on financial transaction requests, leaving the system vulnerable to duplicate transactions from client retries after network timeouts
- —
Choosing DynamoDB for the core ledger purely for its scalability advantages without accounting for the relational integrity, complex ACID transaction, and ad-hoc audit-query needs a ledger specifically has
- —
Treating compliance tooling (CloudTrail, KMS, GuardDuty) as optional add-ons rather than integral, load-bearing parts of the system design from the start — retrofitting audit logging after the fact often means gaps in historical records that can't be recovered
In the AWS Console
- 1
RDS → Databases → Create database
Create an RDS/Aurora instance for the core ledger within private subnets, with a security group scoped only to the application tier.
- 2
KMS → Customer managed keys → Create key, then attach during RDS creation → Additional configuration → Encryption
Create a customer-managed KMS key and enable encryption at rest for the RDS instance using it.
- 3
CloudTrail → Trails → Create trail
Enable a CloudTrail trail logging management events (and relevant data events) to a dedicated, access-restricted S3 bucket.
🎤 Interview questions
Why does a banking system's core ledger need strict ACID transactions, when many of the other system designs in this course lean on eventual consistency? (Listen for: a financial ledger absolutely cannot allow lost updates, dirty reads, or partial writes — a transfer between two accounts must debit one and credit the other atomically, with no window where money appears to exist in both places or neither; this is exactly the scenario relational databases' ACID guarantees (via RDS/Aurora transactions) are designed for, unlike, say, a product catalog where slightly stale data causes no real harm)
What is a double-entry ledger, and why is it the standard design for financial systems? (Listen for: every transaction is recorded as at least two balanced entries — a debit to one account and a matching credit to another, always summing to zero — rather than simply updating a single balance field; this makes the ledger self-auditing (the books must always balance) and provides a complete, immutable transaction history rather than just a current balance, which is essential for both correctness verification and regulatory audit requirements)
How would you prevent a duplicate transfer if a client retries a request after a network timeout, not knowing whether the original request actually succeeded? (Listen for: require an idempotency key on every financial transaction request, generated client-side and checked server-side (typically as a unique constraint in the transactions table) before processing — if a request with an already-processed idempotency key arrives again, return the original result rather than processing the transfer a second time, which is critical since a duplicate financial transaction is a far more serious bug than in almost any other domain)
What AWS services would you use to meet typical financial compliance and audit requirements for this system? (Listen for: CloudTrail for a complete, immutable audit log of every API call/administrative action; KMS for encryption of data at rest, ideally with customer-managed keys for tighter control and key-rotation policies; GuardDuty/Security Hub for continuous threat detection and compliance-standard scoring; VPC with strict network segmentation and no direct internet exposure for the core ledger database; IAM with least-privilege roles and mandatory MFA for any human access to production systems)
Why might a banking system use RDS/Aurora rather than DynamoDB for its core ledger, given DynamoDB's scalability advantages seen in other designs in this course? (Listen for: the core ledger's requirement for strict multi-row ACID transactions (debit one account, credit another, atomically, with full rollback on any failure) is a natural fit for a relational database's transaction model; DynamoDB does support transactions (TransactWriteItems) across multiple items, but the ledger's need for complex relational integrity constraints, foreign keys, and ad-hoc auditor queries across related tables still often favors a relational database for this specific core-ledger component, even while other, less consistency-critical parts of the same banking platform (session data, notification preferences) might reasonably use DynamoDB)