Banking System
Part of the Object Oriented Programming section of Coddy's GO journey — lesson 106 of 107.
Challenge
EasyLet'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 aTransactableinterface withDeposit(amount float64) errorandWithdraw(amount float64) errormethods. Then create two account types that implement this interface:A
CheckingAccountwith unexportedid,holder, andbalancefields. Checking accounts allow withdrawals that bring the balance to exactly zero but not below.A
SavingsAccountwith the same unexported fields plus aminBalancefield. Savings accounts must maintain a minimum balance of 100.0—withdrawals that would drop below this threshold should fail.Both account types need constructors (
NewCheckingAccountandNewSavingsAccount) and aBalance() float64getter method. For deposits, reject negative or zero amounts. For withdrawals, reject negative or zero amounts and amounts exceeding available funds.transaction.go: Create aTransactionstruct to record account activity. Each transaction has aType(string: "deposit" or "withdrawal"),Amount(float64), andAccountID(string). Include aNewTransactionconstructor and aString() stringmethod that returns the format:[Type] $[Amount] on [AccountID]with the amount formatted to two decimal places.bank.go: Build aBankstruct 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 collectionsAddAccount(id string, account Transactable)— registers an accountDeposit(accountID string, amount float64) error— deposits to an account and records the transaction on successWithdraw(accountID string, amount float64) error— withdraws from an account and records the transaction on successTransfer(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 existErrInsufficientFunds— when withdrawal exceeds available balanceErrInvalidAmount— when deposit/withdrawal amount is zero or negativeErrMinBalanceRequired— 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 (
checkingorsavings), 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:
OKfor successful deposits/withdrawals/transfers, the balance formatted to two decimal places for balance queries, orError: [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 ACC002Your output should be:
OK
700.00
Error: minimum balance required
Error: insufficient funds
OK
1000.00And given:
1
checking
C100
Charlie
100.00
4
withdraw C100 100
balance C100
deposit C100 -50
withdraw C999 50Your output should be:
OK
0.00
Error: invalid amount
Error: account not foundNotice 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
1Fundamentals of Go OOP
External FilesGo Workspace & ModulesPackages & ImportsExported vs Unexported NamesIntroduction to OOP in GoStructs as ClassesDefining Methods on StructsPointer vs Value ReceiversStruct InitializationConstructor FunctionsRecap - Simple Calculator4Interfaces
Introduction to InterfacesImplicit ImplementationInterface as ContractEmpty Interface (any)Type AssertionType SwitchInterface CompositionStringer & Error InterfacesRecap - Shape Calculator7Encapsulation
Exported vs Unexported FieldsPackage-Level EncapsulationGetter & Setter MethodsInformation Hiding in GoRecap - Student Records10Generics (Go 1.18+)
Introduction to GenericsType ParametersType ConstraintsGeneric StructsGeneric Methods WorkaroundRecap - Generic Collection2Types & Structs Deep Dive
Basic & Composite TypesCustom Type DefinitionsStruct TagsAnonymous StructsNested StructsZero Values & DefaultsRecap - Contact Book5Composition Over Inheritance
Why Go Has No InheritanceStruct Embedding BasicsMethod PromotionEmbedding Multiple StructsEmbedding vs AggregationShadowing Embedded MethodsRecap - Employee Hierarchy8Error Handling & OOP
The error InterfaceCustom Error TypesError Wrapping (fmt.Errorf)Sentinel Errorserrors.Is() and errors.As()Panic, Defer, and RecoverRecap - File Parser3Pointers & Memory
Pointer Basics in GoPointers to StructsPass by Value vs ReferenceThe new() FunctionGarbage Collection in GoRecap - Linked List Builder6Polymorphism in Go
Polymorphism via InterfacesDuck Typing in GoInterface Satisfaction RulesPolymorphic CollectionsDependency InjectionRecap - Payment Processor9Concurrency & OOP
Goroutines BasicsChannels & CommunicationBuffered vs Unbuffered ChanSelect Statementsync.Mutex & sync.RWMutexsync.WaitGroupThread-Safe Struct DesignRecap - Worker Pool