DEV Community

Dev Cookies
Dev Cookies

Posted on

Banking Ledger System Low-Level Design (LLD) in Java

Complete Interview Guide | Double-Entry Accounting | ACID Transactions | Production-Ready Architecture


Introduction

A Banking Ledger System is one of the most practical Low-Level Design (LLD) interview questions because it combines:

  • Object-Oriented Design (OOD)
  • Financial Domain Modeling
  • ACID Transactions
  • Double-Entry Accounting
  • Concurrency Control
  • Auditability
  • Idempotency
  • Event-Driven Architecture

Companies such as Amazon, Microsoft, Stripe, PayPal, Razorpay, PhonePe, Visa, Mastercard, and banks frequently discuss ledger concepts in interviews.

A ledger is the source of truth for all financial transactions. Account balances should be derived from ledger entries, not maintained as the primary source of truth.


Problem Statement

Design a Banking Ledger System that supports:

  • Create accounts
  • Credit money
  • Debit money
  • Transfer funds
  • View transaction history
  • Maintain account balances
  • Audit every transaction
  • Ensure atomicity and consistency
  • Prevent duplicate processing (idempotency)

Functional Requirements

Core Features

  • Create customer accounts
  • Record deposits
  • Record withdrawals
  • Transfer between accounts
  • Retrieve ledger history
  • Query account balance
  • Reverse transactions
  • Idempotent transaction processing

Non-Functional Requirements

  • ACID compliant
  • Highly available
  • Strong consistency
  • Thread-safe
  • Fault tolerant
  • Immutable audit trail
  • Horizontally scalable

Real-World Use Cases

  • Bank accounts
  • Digital wallets
  • UPI transactions
  • Credit card systems
  • Payment gateways
  • Trading platforms
  • Payroll systems
  • Expense management

Core Banking Principle: Double-Entry Accounting

Every financial transaction creates at least two ledger entries.

Transfer ₹1000

Alice Account      -1000 (Debit)
Bob Account        +1000 (Credit)

Total Change = 0
Enter fullscreen mode Exit fullscreen mode

This guarantees:

  • No money is created unexpectedly
  • No money disappears unexpectedly
  • Every movement is traceable

High-Level Architecture

                    Client
                       │
                       ▼
                Ledger Service
                       │
      ┌────────────────┼────────────────┐
      ▼                ▼                ▼
 Account Service   Transaction Engine   Ledger Store
      │                │                │
      ▼                ▼                ▼
 Account Repo     Journal Entries    Database
                       │
                       ▼
                  Audit Log
Enter fullscreen mode Exit fullscreen mode

Core Components

Component Responsibility
Account Customer account
LedgerEntry Immutable debit/credit record
Transaction Groups ledger entries
LedgerService Business operations
BalanceCalculator Computes balances
AuditService Tracks changes
TransactionRepository Stores transactions

Domain Model

                  +----------------------+
                  |      Account         |
                  +----------------------+
                  | accountId            |
                  | customerId           |
                  | status               |
                  +----------+-----------+
                             |
                             |
                     1        |       *
                             |
                             ▼
                  +----------------------+
                  |    LedgerEntry       |
                  +----------------------+
                  | transactionId        |
                  | accountId            |
                  | amount               |
                  | type                 |
                  | timestamp            |
                  +----------+-----------+
                             |
                             |
                             ▼
                  +----------------------+
                  |    Transaction       |
                  +----------------------+
                  | transactionId        |
                  | status               |
                  | referenceId          |
                  +----------------------+
Enter fullscreen mode Exit fullscreen mode

Design Patterns Used

Pattern Purpose
Strategy Balance calculation, fee policies
Factory Create transaction types
Builder Construct immutable transactions
Command Represent financial operations
State Transaction lifecycle
Observer Notifications and auditing
Repository Data access abstraction
Singleton Configuration services

Enumerations

Entry Type

public enum EntryType {

    DEBIT,
    CREDIT

}
Enter fullscreen mode Exit fullscreen mode

Transaction Status

public enum TransactionStatus {

    PENDING,
    SUCCESS,
    FAILED,
    REVERSED

}
Enter fullscreen mode Exit fullscreen mode

Account

public class Account {

    private final String accountId;

    private final String customerId;

    private AccountStatus status;

    // getters

}
Enter fullscreen mode Exit fullscreen mode

Ledger Entry

import java.math.BigDecimal;
import java.time.Instant;

public class LedgerEntry {

    private final String transactionId;

    private final String accountId;

    private final EntryType type;

    private final BigDecimal amount;

    private final Instant timestamp;

    public LedgerEntry(
            String transactionId,
            String accountId,
            EntryType type,
            BigDecimal amount) {

        this.transactionId = transactionId;
        this.accountId = accountId;
        this.type = type;
        this.amount = amount;
        this.timestamp = Instant.now();
    }

}
Enter fullscreen mode Exit fullscreen mode

Transaction

import java.util.List;

public class Transaction {

    private final String transactionId;

    private final List<LedgerEntry> entries;

    private TransactionStatus status;

    private final String idempotencyKey;

    public Transaction(
            String transactionId,
            List<LedgerEntry> entries,
            String idempotencyKey) {

        this.transactionId = transactionId;
        this.entries = entries;
        this.idempotencyKey = idempotencyKey;
        this.status = TransactionStatus.PENDING;
    }

}
Enter fullscreen mode Exit fullscreen mode

Ledger Service

public interface LedgerService {

    void deposit(String accountId, BigDecimal amount);

    void withdraw(String accountId, BigDecimal amount);

    void transfer(
            String fromAccount,
            String toAccount,
            BigDecimal amount);

}
Enter fullscreen mode Exit fullscreen mode

Transfer Flow

Client

   │

   ▼

Validate Accounts

   │

   ▼

Check Balance

   │

   ▼

Create Transaction

   │

   ▼

Create Debit Entry

   │

   ▼

Create Credit Entry

   │

   ▼

Persist Both Entries (Single DB Transaction)

   │

   ▼

Commit

   │

   ▼

Publish Event
Enter fullscreen mode Exit fullscreen mode

Transfer Example

Transfer ₹500

Transaction ID : TX100

Ledger

--------------------------------

Alice

Debit

₹500

Bob

Credit

₹500
Enter fullscreen mode Exit fullscreen mode

Balance Calculation

Recommended approach: derive the balance from immutable ledger entries.

Balance = Σ(Credits) − Σ(Debits)
Enter fullscreen mode Exit fullscreen mode

For performance, many production systems maintain a cached/materialized balance that is derived from and periodically reconciled against the ledger.


Idempotency

Every external financial request should include an Idempotency Key.

POST /transfer

Idempotency-Key: abc123
Enter fullscreen mode Exit fullscreen mode

If the same request is retried:

Already Processed

Return Previous Result
Enter fullscreen mode Exit fullscreen mode

This prevents duplicate transfers caused by retries or network failures.


Database Schema

Account

Column Type
account_id UUID
customer_id UUID
status ENUM

Transaction

Column Type
transaction_id UUID
status ENUM
idempotency_key VARCHAR (UNIQUE)
created_at TIMESTAMP

Ledger Entry

Column Type
entry_id UUID
transaction_id UUID
account_id UUID
entry_type ENUM
amount DECIMAL
created_at TIMESTAMP

Concurrency Control

Use:

  • Database transactions
  • Optimistic locking (version columns)
  • Pessimistic locking for high-contention accounts
  • Consistent account lock ordering to avoid deadlocks
  • Idempotency keys
  • Retry with exponential backoff for transient conflicts

Thread Safety

Prefer:

  • Stateless services
  • ACID database transactions
  • Immutable ledger entries
  • Optimistic locking

Avoid:

  • Updating balances without transaction boundaries
  • Shared mutable in-memory state as the source of truth

Transaction Lifecycle

             CREATED
                 │
                 ▼
             VALIDATED
                 │
                 ▼
             PENDING
                 │
        ┌────────┴────────┐
        ▼                 ▼
     SUCCESS           FAILED
        │
        ▼
     REVERSED
Enter fullscreen mode Exit fullscreen mode

Production Enhancements

  • Event sourcing
  • Outbox Pattern for reliable event publishing
  • CDC (Change Data Capture)
  • Multi-currency support
  • Exchange rate service
  • Scheduled reconciliation jobs
  • Fraud detection
  • AML/KYC integration
  • Settlement engine
  • Ledger snapshots
  • Read replicas for reporting
  • Encryption of sensitive data
  • Comprehensive audit logs

SOLID Principles

Principle Application
Single Responsibility Separate services for ledger, accounts, auditing, and balance calculation.
Open/Closed New transaction or fee types can be added without modifying existing logic.
Liskov Substitution Different repository implementations can be substituted transparently.
Interface Segregation Small interfaces (LedgerService, AccountRepository, AuditService).
Dependency Inversion Business logic depends on abstractions rather than database implementations.

Complexity Analysis

Operation Complexity
Create Transaction O(1)
Create Ledger Entries O(1)
Transfer O(1) (excluding I/O)
Balance Calculation O(n) over ledger entries
Cached Balance Lookup O(1)

Common Interview Follow-up Questions

  1. Why use double-entry accounting instead of updating balances directly?
  2. How would you guarantee atomicity across debit and credit entries?
  3. How would you prevent duplicate transfers?
  4. How would you implement transaction reversal?
  5. How would you reconcile cached balances with the ledger?
  6. How would you support millions of transactions per day?
  7. How would you shard ledger data while preserving consistency?
  8. When would you use optimistic locking versus pessimistic locking?
  9. How would you design the ledger for multi-currency transactions?
  10. How would you integrate the ledger with Kafka using the Outbox Pattern?

Interview Tips

Explain the Difference Between Account Balance and Ledger

  • Ledger: Immutable source of truth containing every financial movement.
  • Balance: A derived or cached value computed from the ledger.

Emphasize Financial Correctness

  • Double-entry accounting
  • ACID transactions
  • Idempotency
  • Immutable audit trail
  • Consistency over availability for core financial operations

Mention Production Considerations

  • Event sourcing (where appropriate)
  • Outbox Pattern + Kafka
  • CDC for downstream systems
  • Optimistic locking
  • Reconciliation jobs
  • Monitoring and alerting
  • Disaster recovery and backups

Key Takeaways

  • Model every financial operation as an immutable Transaction containing balanced Ledger Entries.
  • Use double-entry accounting to ensure every debit has a corresponding credit.
  • Treat the ledger as the source of truth; balances are derived or cached views.
  • Ensure ACID transactions, idempotency, and auditability for every operation.
  • Design for extensibility using Repository, Strategy, Factory, and Command patterns.
  • Discuss concurrency, reconciliation, distributed messaging, and operational concerns to demonstrate senior-level system design expertise.

Top comments (0)