Facebook Pixel

2043. Simple Bank System

MediumDesignArrayHash TableSimulation
Leetcode Link

Problem Description

This problem asks you to implement a simple banking system that can handle three types of transactions: transfers, deposits, and withdrawals.

You need to create a Bank class with the following features:

Initial Setup:

  • The bank has n accounts numbered from 1 to n (note: 1-indexed)
  • You're given an initial balance array where balance[i] represents the initial balance of account (i + 1)

Operations to Implement:

  1. Constructor Bank(balance): Initialize the bank with the given balance array

  2. transfer(account1, account2, money): Transfer money dollars from account1 to account2

    • Returns true if successful, false otherwise
    • The transaction is valid only if:
      • Both account numbers are between 1 and n
      • account1 has sufficient balance (≥ money)
  3. deposit(account, money): Add money dollars to the specified account

    • Returns true if successful, false otherwise
    • The transaction is valid only if the account number is between 1 and n
  4. withdraw(account, money): Remove money dollars from the specified account

    • Returns true if successful, false otherwise
    • The transaction is valid only if:
      • The account number is between 1 and n
      • The account has sufficient balance (≥ money)

Key Points:

  • Account numbers are 1-indexed (accounts are numbered 1 to n)
  • The balance array is 0-indexed (standard array indexing)
  • All invalid transactions should return false without modifying any balances
  • Valid transactions should update the balances and return true
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is that this is a straightforward simulation problem - we need to maintain the state of all bank accounts and update them based on valid transactions.

Since we need to track the balance of multiple accounts and access them by account number, an array is the natural choice for storing balances. The main challenge is handling the index conversion correctly: accounts are numbered from 1 to n, but arrays are 0-indexed. So account k corresponds to balance[k-1].

For each operation, we follow a simple pattern:

  1. First validate - check if the transaction can proceed
  2. Then execute - only modify balances if validation passes

The validation logic follows directly from the requirements:

  • For any operation, the account number must be valid (between 1 and n)
  • For operations that remove money (withdraw and transfer), we must ensure sufficient balance exists

Why check account validity as account > n instead of account < 1 || account > n? Since account numbers are positive integers in this context, we only need to check the upper bound. Invalid negative or zero account numbers would naturally fail this check.

The beauty of this solution is its simplicity - we don't need complex data structures or algorithms. Just an array to store balances and careful validation before each operation. Each method performs constant time operations: array access, comparison, and basic arithmetic, making the solution efficient.

The separation of validation and execution also makes the code clean and maintainable - if a transaction fails validation, we return false immediately without touching any balances, ensuring data consistency.

Solution Approach

The implementation uses a simple simulation approach with an array to store account balances.

Data Structure:

  • Use an array balance to store the balance of each account
  • Store the total number of accounts n as a member variable for quick validation

Implementation Details:

  1. Constructor (__init__):

    • Store the input balance array directly as a member variable
    • Calculate and store n = len(balance) for boundary checking
    • This gives us O(1) access to any account's balance
  2. Transfer Function:

    • Validation: Check if account1 > n or account2 > n (invalid account numbers)
    • Check if balance[account1 - 1] < money (insufficient funds)
    • If any validation fails, return false
    • Otherwise, execute the transfer:
      • balance[account1 - 1] -= money (deduct from source)
      • balance[account2 - 1] += money (add to destination)
    • Return true after successful transfer
  3. Deposit Function:

    • Validation: Check if account > n (invalid account number)
    • If validation fails, return false
    • Otherwise, add money: balance[account - 1] += money
    • Return true after successful deposit
  4. Withdraw Function:

    • Validation: Check if account > n (invalid account number)
    • Check if balance[account - 1] < money (insufficient funds)
    • If any validation fails, return false
    • Otherwise, deduct money: balance[account - 1] -= money
    • Return true after successful withdrawal

Index Conversion: The critical detail is converting between 1-indexed account numbers and 0-indexed array positions. For account number k, we access balance[k - 1] in the array.

Time Complexity:

  • All operations (transfer, deposit, withdraw) run in O(1) time
  • Each operation performs a fixed number of array accesses and comparisons

Space Complexity:

  • O(n) space to store the balance array where n is the number of accounts

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a small example to illustrate how the banking system works.

Initial Setup:

balance = [100, 50, 200]

This creates a bank with 3 accounts:

  • Account 1: $100 (stored at index 0)
  • Account 2: $50 (stored at index 1)
  • Account 3: $200 (stored at index 2)

Operation 1: Transfer $30 from Account 1 to Account 3

transfer(1, 3, 30)
  • Validation:
    • Is account 1 valid? 1 ≤ 3 ✓
    • Is account 3 valid? 3 ≤ 3 ✓
    • Does account 1 have ≥ $30? balance[0] = 100 ≥ 30 ✓
  • Execution:
    • balance[0] = 100 - 30 = 70
    • balance[2] = 200 + 30 = 230
  • Result: Returns true
  • New balances: [70, 50, 230]

Operation 2: Withdraw $80 from Account 2

withdraw(2, 80)
  • Validation:
    • Is account 2 valid? 2 ≤ 3 ✓
    • Does account 2 have ≥ $80? balance[1] = 50 < 80 ✗
  • Result: Returns false (insufficient funds)
  • Balances unchanged: [70, 50, 230]

Operation 3: Deposit $25 to Account 2

deposit(2, 25)
  • Validation:
    • Is account 2 valid? 2 ≤ 3 ✓
  • Execution:
    • balance[1] = 50 + 25 = 75
  • Result: Returns true
  • New balances: [70, 75, 230]

Operation 4: Transfer $100 from Account 4 to Account 1

transfer(4, 1, 100)
  • Validation:
    • Is account 4 valid? 4 > 3 ✗ (invalid account)
  • Result: Returns false (invalid account)
  • Balances unchanged: [70, 75, 230]

This example demonstrates the key aspects:

  1. Index conversion: Account 1 maps to index 0, Account 2 to index 1, etc.
  2. Validation before execution: Each operation checks validity first
  3. Balance preservation: Failed operations don't modify any balances
  4. Successful updates: Valid operations update the appropriate balances and return true

Solution Implementation

1from typing import List
2
3class Bank:
4    def __init__(self, balance: List[int]):
5        """
6        Initialize the bank with initial account balances.
7      
8        Args:
9            balance: List of initial balances where balance[i] is the balance of account i+1
10        """
11        self.balance = balance
12        self.n = len(balance)
13
14    def transfer(self, account1: int, account2: int, money: int) -> bool:
15        """
16        Transfer money from account1 to account2.
17      
18        Args:
19            account1: Source account number (1-indexed)
20            account2: Destination account number (1-indexed)
21            money: Amount to transfer
22          
23        Returns:
24            True if transfer successful, False otherwise
25        """
26        # Check if accounts are valid and source account has sufficient funds
27        if account1 > self.n or account2 > self.n or self.balance[account1 - 1] < money:
28            return False
29      
30        # Perform the transfer
31        self.balance[account1 - 1] -= money
32        self.balance[account2 - 1] += money
33        return True
34
35    def deposit(self, account: int, money: int) -> bool:
36        """
37        Deposit money into an account.
38      
39        Args:
40            account: Account number (1-indexed)
41            money: Amount to deposit
42          
43        Returns:
44            True if deposit successful, False otherwise
45        """
46        # Check if account is valid
47        if account > self.n:
48            return False
49      
50        # Add money to the account
51        self.balance[account - 1] += money
52        return True
53
54    def withdraw(self, account: int, money: int) -> bool:
55        """
56        Withdraw money from an account.
57      
58        Args:
59            account: Account number (1-indexed)
60            money: Amount to withdraw
61          
62        Returns:
63            True if withdrawal successful, False otherwise
64        """
65        # Check if account is valid and has sufficient funds
66        if account > self.n or self.balance[account - 1] < money:
67            return False
68      
69        # Withdraw money from the account
70        self.balance[account - 1] -= money
71        return True
72
73
74# Your Bank object will be instantiated and called as such:
75# obj = Bank(balance)
76# param_1 = obj.transfer(account1, account2, money)
77# param_2 = obj.deposit(account, money)
78# param_3 = obj.withdraw(account, money)
79
1/**
2 * Bank class that simulates basic banking operations
3 * Accounts are 1-indexed (account numbers start from 1)
4 */
5class Bank {
6    // Array to store account balances
7    private long[] accountBalances;
8    // Total number of accounts in the bank
9    private int numberOfAccounts;
10
11    /**
12     * Constructor to initialize the bank with initial account balances
13     * @param balance Array of initial balances for each account
14     */
15    public Bank(long[] balance) {
16        this.accountBalances = balance;
17        this.numberOfAccounts = balance.length;
18    }
19
20    /**
21     * Transfer money from one account to another
22     * @param account1 Source account number (1-indexed)
23     * @param account2 Destination account number (1-indexed)
24     * @param money Amount of money to transfer
25     * @return true if transfer is successful, false otherwise
26     */
27    public boolean transfer(int account1, int account2, long money) {
28        // Validate both account numbers exist and source account has sufficient balance
29        if (account1 > numberOfAccounts || account2 > numberOfAccounts || 
30            accountBalances[account1 - 1] < money) {
31            return false;
32        }
33      
34        // Deduct money from source account
35        accountBalances[account1 - 1] -= money;
36        // Add money to destination account
37        accountBalances[account2 - 1] += money;
38      
39        return true;
40    }
41
42    /**
43     * Deposit money into an account
44     * @param account Account number (1-indexed)
45     * @param money Amount of money to deposit
46     * @return true if deposit is successful, false otherwise
47     */
48    public boolean deposit(int account, long money) {
49        // Validate account number exists
50        if (account > numberOfAccounts) {
51            return false;
52        }
53      
54        // Add money to the account
55        accountBalances[account - 1] += money;
56      
57        return true;
58    }
59
60    /**
61     * Withdraw money from an account
62     * @param account Account number (1-indexed)
63     * @param money Amount of money to withdraw
64     * @return true if withdrawal is successful, false otherwise
65     */
66    public boolean withdraw(int account, long money) {
67        // Validate account number exists and has sufficient balance
68        if (account > numberOfAccounts || accountBalances[account - 1] < money) {
69            return false;
70        }
71      
72        // Deduct money from the account
73        accountBalances[account - 1] -= money;
74      
75        return true;
76    }
77}
78
79/**
80 * Your Bank object will be instantiated and called as such:
81 * Bank obj = new Bank(balance);
82 * boolean param_1 = obj.transfer(account1,account2,money);
83 * boolean param_2 = obj.deposit(account,money);
84 * boolean param_3 = obj.withdraw(account,money);
85 */
86
1class Bank {
2private:
3    vector<long long> accountBalances;  // Store balance for each account
4    int totalAccounts;                  // Total number of accounts in the bank
5  
6public:
7    /**
8     * Constructor: Initialize bank with given account balances
9     * @param balance: Initial balance for each account (1-indexed in usage)
10     */
11    Bank(vector<long long>& balance) {
12        accountBalances = balance;
13        totalAccounts = balance.size();
14    }
15  
16    /**
17     * Transfer money from one account to another
18     * @param account1: Source account number (1-indexed)
19     * @param account2: Destination account number (1-indexed)
20     * @param money: Amount to transfer
21     * @return: true if transfer successful, false otherwise
22     */
23    bool transfer(int account1, int account2, long long money) {
24        // Validate both accounts exist and source account has sufficient funds
25        if (account1 < 1 || account1 > totalAccounts || 
26            account2 < 1 || account2 > totalAccounts || 
27            accountBalances[account1 - 1] < money) {
28            return false;
29        }
30      
31        // Perform the transfer
32        accountBalances[account1 - 1] -= money;
33        accountBalances[account2 - 1] += money;
34        return true;
35    }
36  
37    /**
38     * Deposit money into an account
39     * @param account: Account number (1-indexed)
40     * @param money: Amount to deposit
41     * @return: true if deposit successful, false otherwise
42     */
43    bool deposit(int account, long long money) {
44        // Validate account exists
45        if (account < 1 || account > totalAccounts) {
46            return false;
47        }
48      
49        // Add money to the account
50        accountBalances[account - 1] += money;
51        return true;
52    }
53  
54    /**
55     * Withdraw money from an account
56     * @param account: Account number (1-indexed)
57     * @param money: Amount to withdraw
58     * @return: true if withdrawal successful, false otherwise
59     */
60    bool withdraw(int account, long long money) {
61        // Validate account exists and has sufficient funds
62        if (account < 1 || account > totalAccounts || 
63            accountBalances[account - 1] < money) {
64            return false;
65        }
66      
67        // Deduct money from the account
68        accountBalances[account - 1] -= money;
69        return true;
70    }
71};
72
73/**
74 * Your Bank object will be instantiated and called as such:
75 * Bank* obj = new Bank(balance);
76 * bool param_1 = obj->transfer(account1, account2, money);
77 * bool param_2 = obj->deposit(account, money);
78 * bool param_3 = obj->withdraw(account, money);
79 */
80
1// Array to store account balances
2let accountBalances: number[] = [];
3
4/**
5 * Initializes the bank with given account balances
6 * @param balance - Array of initial balances for each account
7 */
8function initializeBank(balance: number[]): void {
9    accountBalances = balance;
10}
11
12/**
13 * Transfers money from one account to another
14 * @param account1 - Source account number (1-indexed)
15 * @param account2 - Destination account number (1-indexed)
16 * @param money - Amount to transfer
17 * @returns true if transfer successful, false otherwise
18 */
19function transfer(account1: number, account2: number, money: number): boolean {
20    // Validate account numbers and check sufficient balance
21    if (
22        account1 > accountBalances.length ||
23        account2 > accountBalances.length ||
24        money > accountBalances[account1 - 1]
25    ) {
26        return false;
27    }
28  
29    // Perform the transfer
30    accountBalances[account1 - 1] -= money;
31    accountBalances[account2 - 1] += money;
32  
33    return true;
34}
35
36/**
37 * Deposits money into an account
38 * @param account - Account number (1-indexed)
39 * @param money - Amount to deposit
40 * @returns true if deposit successful, false otherwise
41 */
42function deposit(account: number, money: number): boolean {
43    // Validate account number
44    if (account > accountBalances.length) {
45        return false;
46    }
47  
48    // Add money to the account
49    accountBalances[account - 1] += money;
50  
51    return true;
52}
53
54/**
55 * Withdraws money from an account
56 * @param account - Account number (1-indexed)
57 * @param money - Amount to withdraw
58 * @returns true if withdrawal successful, false otherwise
59 */
60function withdraw(account: number, money: number): boolean {
61    // Validate account number and check sufficient balance
62    if (
63        account > accountBalances.length || 
64        money > accountBalances[account - 1]
65    ) {
66        return false;
67    }
68  
69    // Deduct money from the account
70    accountBalances[account - 1] -= money;
71  
72    return true;
73}
74

Time and Space Complexity

Time Complexity: Each operation (transfer, deposit, withdraw) has a time complexity of O(1). All operations perform a constant number of operations regardless of the input size - they only involve simple comparisons, arithmetic operations, and direct array access using indices, which all take constant time.

Space Complexity: The overall space complexity is O(n), where n is the length of the balance list. The class stores the balance list which contains n elements representing account balances. The additional variables self.n and temporary variables within methods use O(1) space, so the dominant factor is the storage of the balance list itself.

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Forgetting to Check Lower Bound for Account Numbers

The current implementation only checks if account numbers exceed n but doesn't verify they're at least 1. Since accounts are 1-indexed, account number 0 or negative numbers would cause incorrect array access.

Problem Example:

bank = Bank([100, 200])
bank.withdraw(0, 50)  # Would access balance[-1], which is valid but wrong!
bank.transfer(-1, 1, 50)  # Would access balance[-2], also valid but wrong!

Solution: Add lower bound validation to all methods:

def transfer(self, account1: int, account2: int, money: int) -> bool:
    # Check both upper AND lower bounds
    if (account1 < 1 or account1 > self.n or 
        account2 < 1 or account2 > self.n or 
        self.balance[account1 - 1] < money):
        return False
    # ... rest of the code

def deposit(self, account: int, money: int) -> bool:
    if account < 1 or account > self.n:
        return False
    # ... rest of the code

def withdraw(self, account: int, money: int) -> bool:
    if account < 1 or account > self.n or self.balance[account - 1] < money:
        return False
    # ... rest of the code

2. Not Validating Negative Money Amounts

The implementation doesn't check if the money amount is negative. While some systems might want to allow this (negative deposit = withdrawal), it's typically a bug and can lead to unexpected behavior.

Problem Example:

bank = Bank([100, 200])
bank.deposit(1, -50)  # Would actually subtract money!
bank.withdraw(1, -50)  # Would actually add money!
bank.transfer(1, 2, -50)  # Would transfer money backwards!

Solution: Add validation for non-negative amounts:

def transfer(self, account1: int, account2: int, money: int) -> bool:
    if money < 0:
        return False
    # ... rest of validation
  
def deposit(self, account: int, money: int) -> bool:
    if money < 0:
        return False
    # ... rest of validation

def withdraw(self, account: int, money: int) -> bool:
    if money < 0:
        return False
    # ... rest of validation

3. Integer Overflow Concerns

In languages with fixed-size integers or when dealing with very large amounts, adding money to an account could cause integer overflow.

Problem Example:

# If using a language with fixed integer size
bank = Bank([2**31 - 100])  # Near max int
bank.deposit(1, 200)  # Could overflow in some languages

Solution: Check for potential overflow before performing operations:

def deposit(self, account: int, money: int) -> bool:
    if account < 1 or account > self.n:
        return False
  
    # Check for potential overflow (Python handles this automatically, 
    # but other languages might need explicit checks)
    if self.balance[account - 1] > sys.maxsize - money:
        return False  # Would overflow
  
    self.balance[account - 1] += money
    return True

4. Modifying the Original Balance Array

The constructor stores a reference to the input array rather than creating a copy. If the caller modifies the original array after creating the Bank object, it could corrupt the bank's state.

Problem Example:

initial_balance = [100, 200]
bank = Bank(initial_balance)
initial_balance[0] = 1000  # This modifies the bank's internal state!

Solution: Create a defensive copy of the input array:

def __init__(self, balance: List[int]):
    self.balance = balance.copy()  # Create a copy instead of using reference
    self.n = len(self.balance)
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

In a binary min heap, the maximum element can be found in:


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More