SYSTEM_DESIGN
System Design: Core Banking System
Design a core banking system handling account management, transaction processing, interest calculation, and multi-currency operations with ACID compliance, regulatory reporting, and 24/7 availability.
Requirements
Functional Requirements:
- Manage customer accounts (checking, savings, fixed deposits, loans) with full lifecycle
- Process real-time transactions: deposits, withdrawals, transfers (internal and interbank via ACH/wire/SWIFT)
- Calculate and apply interest accruals (daily calculation, monthly posting for savings; daily for loans)
- Multi-currency account support with real-time FX rate application
- Generate regulatory reports (Call Reports, CCAR stress tests, Basel III capital adequacy)
- Support standing instructions, scheduled payments, and recurring transfers
Non-Functional Requirements:
- ACID compliance for all financial mutations — zero tolerance for balance inconsistencies
- Process 5,000 transactions/sec with sub-second response for teller operations
- 99.999% availability (five nines) — bank downtime means ATMs stop working
- Full audit trail with immutable transaction history for 7+ years (regulatory retention)
- Data sovereignty: customer data must reside in the country of account domicile
Scale Estimation
A mid-size bank with 20M customer accounts processes 50M transactions/day = 580 TPS average, peaking at 5,000 TPS on payroll processing days (1st and 15th of month when employers batch-submit ACH credits). Interest calculation runs as a nightly batch across all 20M accounts — each account requires reading balance history, applying tiered rates, and writing accrual entries. With 20M accounts at 5ms per calculation = 28 hours sequentially, requiring parallelization across 100 workers to complete within a 4-hour batch window. Statement generation: 20M monthly statements at 10KB average = 200GB of PDF generation per month.
High-Level Architecture
Core banking systems follow a layered architecture reflecting the separation between customer-facing channels and the financial engine. The Channel Layer handles interactions from branches (teller applications), ATMs, mobile banking, internet banking, and partner APIs. All channels funnel through an API Gateway into the Core Banking Engine.
The Core Banking Engine contains the Account Management Service (account lifecycle, KYC data, account configuration), the Transaction Processing Service (the financial state machine that executes debits and credits), the Ledger Service (double-entry bookkeeping with general ledger integration), and the Product Engine (configurable rules for interest rates, fees, limits, and product features). These services share a single ACID-compliant database cluster — unlike microservices that prefer independent databases, core banking requires transactional consistency across accounts, ledger, and transaction records.
The Batch Processing Layer handles end-of-day (EOD) operations: interest accrual, fee assessment, statement generation, regulatory reporting, and interbank settlement file processing (ACH NACHA files, SWIFT MT messages). EOD processing follows a strict sequence: close current business day → accrue interest → assess fees → generate GL entries → produce regulatory feeds → open next business day. This sequence is orchestrated by a batch workflow engine (Spring Batch or custom) with checkpoint/restart capability.
Core Components
Transaction Processing Engine
The Transaction Processing Engine (TPE) is the heart of core banking. Every financial operation is expressed as a transaction containing one or more entries that must balance (total debits = total credits). The TPE processes transactions through a pipeline: validation (account exists, is active, has sufficient funds, within transaction limits) → authorization (hold/earmark funds for debit account) → execution (apply entries to account balances atomically) → posting (write to general ledger). All of this occurs within a single database transaction with SERIALIZABLE isolation. The TPE implements idempotency using a transaction reference number (TRN) unique index — duplicate submissions return the original result. For interbank transfers, the TPE uses a two-phase approach: immediate debit from the sender's account into a suspense account, then settlement via ACH/wire moves funds from suspense to the receiving bank.
Product & Interest Engine
The Product Engine defines account behavior through configurable product templates. Each product (e.g., "Premium Savings Account") specifies: interest rate tiers (0.5% for $0-10K, 1.0% for $10K-50K, 1.5% for $50K+), fee schedule (monthly maintenance fee of $12, waived if balance >$5K), transaction limits (6 withdrawals/month per Reg D), and feature flags (overdraft protection, sweep to investment). Interest calculation uses the daily balance method: each day, the engine reads each account's end-of-day balance, determines the applicable rate tier, computes daily interest (balance × annual_rate / 365), and accumulates it in an accrued_interest field. Monthly, the accrued interest is posted as a transaction crediting the account.
General Ledger Integration
Every transaction in the core banking system generates corresponding general ledger (GL) entries following a chart of accounts structure. A customer deposit creates: DEBIT Cash/Vault (asset increases), CREDIT Customer Deposits (liability increases). The GL module maintains real-time sub-ledger to GL reconciliation — the sum of all customer account balances in the sub-ledger must equal the Customer Deposits GL account balance at all times. Any discrepancy triggers an immediate alert to the finance operations team. Month-end close involves freezing GL balances, running trial balance validation, and generating financial statements (income statement, balance sheet). GL data feeds into regulatory reporting engines that produce Call Reports (FFIEC), FR Y-9C (Federal Reserve), and Basel III capital adequacy calculations.
Database Design
Core banking uses a single PostgreSQL cluster (Patroni-managed with synchronous replication for zero data loss). Key tables: accounts (account_id, customer_id, product_id, account_number, status, currency, current_balance NUMERIC(18,4), available_balance, interest_accrued, opened_date, last_interest_posting_date), transactions (transaction_id, trn VARCHAR UNIQUE, transaction_date, value_date, debit_account_id, credit_account_id, amount, currency, transaction_type, channel, description, created_by), gl_entries (entry_id, transaction_id, gl_account_code, debit_amount, credit_amount, posting_date, business_date).
The balance column uses NUMERIC(18,4) providing up to 14 digits with 4 decimal places — sufficient for amounts up to 99 trillion with sub-cent precision. Historical balances are stored in a balance_history table (account_id, business_date, opening_balance, closing_balance) for interest calculation and regulatory reporting. All tables use logical deletes (status flags) rather than physical deletes — financial records are never removed.
API Design
POST /v1/transactions— Execute a financial transaction; body contains debit_account, credit_account, amount, currency, transaction_type, reference_number (idempotency key), value_date; returns transaction_id, status, updated_balancesGET /v1/accounts/{account_id}/balance— Real-time balance inquiry returning current_balance, available_balance (accounts for holds), interest_accruedGET /v1/accounts/{account_id}/statement?from={date}&to={date}— Account statement with transaction history, opening/closing balance, and interest summaryPOST /v1/batch/eod/trigger— Trigger end-of-day batch processing for a specified business_date; returns batch_job_id for monitoring
Scaling & Bottlenecks
The single-database requirement for ACID consistency is the primary scaling constraint. PostgreSQL handles 5,000 TPS with proper tuning (large shared_buffers, optimized WAL settings, connection pooling via PgBouncer). For banks exceeding this, the database is sharded by account number range — accounts are assigned to shards at creation time, and intra-shard transactions maintain full ACID. Cross-shard transfers (between accounts on different shards) use a saga with a suspense account on each shard: debit sender's shard → credit suspense on receiver's shard → debit suspense and credit receiver. This introduces brief inconsistency (sender debited but receiver not yet credited) bounded to under 100ms.
EOD batch processing is the nightly bottleneck. Interest accrual across 20M accounts must complete within the batch window (typically 11 PM - 3 AM). The batch is parallelized by account range with each worker processing 200K accounts. Checkpoint/restart ensures that if a worker fails, it resumes from the last committed checkpoint rather than reprocessing all accounts. Statement generation is offloaded to a separate read replica to avoid impacting production transaction processing.
Key Trade-offs
- Single database cluster over microservice-per-domain databases: Enables ACID transactions across account, transaction, and GL tables — the regulatory requirement for perfect financial consistency makes this non-negotiable, despite limiting horizontal scalability
- Synchronous replication over asynchronous: Zero data loss guarantee is essential for financial records (RPO=0), but synchronous replication adds 1-2ms to every write — acceptable given the sub-second SLA
- Nightly batch EOD over real-time interest posting: Batch processing is simpler, auditable, and aligns with banking conventions (business date concept), but prevents real-time interest visibility — mitigated by showing accrued (projected) interest in the UI
- NUMERIC(18,4) over floating point: Fixed-precision arithmetic eliminates rounding errors in financial calculations but uses more storage and is slower than floating point — mandatory for financial accuracy
GO DEEPER
Master this topic in our 12-week cohort
Our Advanced System Design cohort covers this and 11 other deep-dive topics with live sessions, assignments, and expert feedback.