Menu
Coddy logo textTech

Banking System

Part of the Object Oriented Programming section of Coddy's GO journey — lesson 106 of 107.

challenge icon

Challenge

Easy

Let's build a Banking System that manages accounts, transactions, and financial operations! You'll create a robust system where data integrity is critical—account balances should only change through validated methods, and errors like overdrafts or invalid amounts must be handled gracefully.

You'll organize your code across five files:

  • account.go: Define a Transactable interface with Deposit(amount float64) error and Withdraw(amount float64) error methods. Then create two account types that implement this interface:

    A CheckingAccount with unexported id, holder, and balance fields. Checking accounts allow withdrawals that bring the balance to exactly zero but not below.

    A SavingsAccount with the same unexported fields plus a minBalance field. Savings accounts must maintain a minimum balance of 100.0—withdrawals that would drop below this threshold should fail.

    Both account types need constructors (NewCheckingAccount and NewSavingsAccount) and a Balance() float64 getter method. For deposits, reject negative or zero amounts. For withdrawals, reject negative or zero amounts and amounts exceeding available funds.

  • transaction.go: Create a Transaction struct to record account activity. Each transaction has a Type (string: "deposit" or "withdrawal"), Amount (float64), and AccountID (string). Include a NewTransaction constructor and a String() string method that returns the format: [Type] $[Amount] on [AccountID] with the amount formatted to two decimal places.
  • bank.go: Build a Bank struct that manages multiple accounts and records transactions. Store accounts in a map by ID and keep a slice of transactions. Implement:
    • NewBank() *Bank — initializes empty collections
    • AddAccount(id string, account Transactable) — registers an account
    • Deposit(accountID string, amount float64) error — deposits to an account and records the transaction on success
    • Withdraw(accountID string, amount float64) error — withdraws from an account and records the transaction on success
    • Transfer(fromID, toID string, amount float64) error — moves money between accounts atomically (if withdrawal succeeds but deposit fails, reverse the withdrawal)
    • GetBalance(accountID string) (float64, error) — returns an account's balance

    Return appropriate errors for non-existent accounts.

  • errors.go: Define sentinel errors for your banking operations:
    • ErrAccountNotFound — when an account ID doesn't exist
    • ErrInsufficientFunds — when withdrawal exceeds available balance
    • ErrInvalidAmount — when deposit/withdrawal amount is zero or negative
    • ErrMinBalanceRequired — when savings withdrawal would violate minimum balance
  • main.go: Create a bank and process operations based on input.

    Read the number of accounts to create. For each account, read the account type (checking or savings), ID, holder name, and initial balance. Create the appropriate account type and add it to the bank.

    Then read the number of operations. Each operation is one of:

    • deposit [accountID] [amount]
    • withdraw [accountID] [amount]
    • transfer [fromID] [toID] [amount]
    • balance [accountID]

    For each operation, print the result: OK for successful deposits/withdrawals/transfers, the balance formatted to two decimal places for balance queries, or Error: [message] for failures using your sentinel error messages.

The following inputs will be provided:

  • Number of accounts, then each account's type, ID, holder, initial balance (4 lines each)
  • Number of operations, then each operation (1 line each)

For example, given:

2
checking
ACC001
Alice
500.00
savings
ACC002
Bob
1000.00
6
deposit ACC001 200
balance ACC001
withdraw ACC002 950
withdraw ACC001 800
transfer ACC001 ACC002 100
balance ACC002

Your output should be:

OK
700.00
Error: minimum balance required
Error: insufficient funds
OK
1000.00

And given:

1
checking
C100
Charlie
100.00
4
withdraw C100 100
balance C100
deposit C100 -50
withdraw C999 50

Your output should be:

OK
0.00
Error: invalid amount
Error: account not found

Notice how the Transactable interface allows the Bank to work with both account types uniformly, while each account type enforces its own rules. The Transfer method demonstrates atomic operations—if something goes wrong partway through, the system stays consistent!

Try it yourself

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

func main() {
	scanner := bufio.NewScanner(os.Stdin)

	// Read number of accounts
	scanner.Scan()
	numAccounts, _ := strconv.Atoi(scanner.Text())

	// Create a new bank
	bank := NewBank()

	// Read and create accounts
	for i := 0; i < numAccounts; i++ {
		scanner.Scan()
		accountType := scanner.Text()
		scanner.Scan()
		id := scanner.Text()
		scanner.Scan()
		holder := scanner.Text()
		scanner.Scan()
		balance, _ := strconv.ParseFloat(scanner.Text(), 64)

		// TODO: Create the appropriate account type based on accountType
		// and add it to the bank
		_ = accountType
		_ = id
		_ = holder
		_ = balance
	}

	// Read number of operations
	scanner.Scan()
	numOps, _ := strconv.Atoi(scanner.Text())

	// Process each operation
	for i := 0; i < numOps; i++ {
		scanner.Scan()
		parts := strings.Fields(scanner.Text())
		operation := parts[0]

		// TODO: Handle each operation type:
		// - "deposit": deposit to account, print "OK" or error
		// - "withdraw": withdraw from account, print "OK" or error
		// - "transfer": transfer between accounts, print "OK" or error
		// - "balance": get and print balance formatted to 2 decimal places
		_ = operation
	}
}

All lessons in Object Oriented Programming