Hone logo
Hone
Problems

Mastering Go Method Receivers: The Bank Account Challenge

Method receivers are a cornerstone of Go's object-oriented-like capabilities, allowing you to attach methods to custom types. This challenge will help you understand and implement both value and pointer receivers for a practical scenario: managing a simple bank account.

Problem Description

Your task is to create a BankAccount type in Go and implement several methods on it using both value and pointer receivers. You'll need to simulate core banking operations like depositing funds, withdrawing funds, and checking the balance. Pay close attention to when each type of receiver is appropriate to ensure your methods behave as expected and modify the bank account's state correctly.

Key Requirements:

  1. Define a BankAccount struct: This struct should hold at least the balance (an integer representing cents) and optionally an accountHolderName (a string).
  2. Implement a Deposit method: This method should accept an amount (in cents) and add it to the account's balance.
  3. Implement a Withdraw method: This method should accept an amount (in cents), check if sufficient funds are available, and if so, subtract the amount from the balance. It should return a boolean indicating success or failure.
  4. Implement a Balance method: This method should return the current balance of the account.
  5. Implement a Transfer method: This method should accept another BankAccount and an amount. It should attempt to withdraw the amount from the current account and deposit it into the target account. This method should also return a boolean indicating success or failure.
  6. Choose appropriate receivers: Decide whether each method should use a value receiver (func (b BankAccount) ...) or a pointer receiver (func (b *BankAccount) ...) and justify your choice.

Expected Behavior:

  • Deposits should always increase the balance.
  • Withdrawals should only succeed if there are enough funds.
  • Balance checks should accurately reflect the current amount.
  • Transfers should correctly debit one account and credit another, only succeeding if the source account has sufficient funds.

Edge Cases to Consider:

  • Attempting to deposit or withdraw negative amounts.
  • Attempting to withdraw more funds than available.
  • Transferring funds to the same account.

Examples

Example 1: Basic Operations

Input:
// Initialize a new account
account := BankAccount{balance: 10000, accountHolderName: "Alice"}

// Perform operations
account.Deposit(5000)
canWithdraw := account.Withdraw(2000)
currentBalance := account.Balance()

Output:
Balance after deposit: 15000
Withdrawal successful: true
Current balance: 13000

Explanation:
The account starts with 10000 cents.
A deposit of 5000 cents brings the balance to 15000 cents.
A withdrawal of 2000 cents is successful, leaving a balance of 13000 cents.
The final balance is 13000 cents.

Example 2: Insufficient Funds and Transfer

Input:
account1 := BankAccount{balance: 7000, accountHolderName: "Bob"}
account2 := BankAccount{balance: 3000, accountHolderName: "Charlie"}

// Attempt an overdraft
canWithdrawLarge := account1.Withdraw(10000)

// Perform a successful transfer
canTransfer := account1.Transfer(account2, 4000)

// Check balances after transfer
balance1 := account1.Balance()
balance2 := account2.Balance()

Output:
Withdrawal of 10000 unsuccessful: false
Transfer of 4000 from Bob to Charlie successful: true
Bob's balance after transfer: 3000
Charlie's balance after transfer: 7000

Explanation:
account1 starts with 7000 cents.
Attempting to withdraw 10000 cents fails due to insufficient funds.
A transfer of 4000 cents from account1 to account2 is successful.
account1's balance becomes 7000 - 4000 = 3000 cents.
account2's balance becomes 3000 + 4000 = 7000 cents.

Example 3: Edge Cases (Negative Values, Self-Transfer)

Input:
account := BankAccount{balance: 5000, accountHolderName: "David"}

// Attempt negative deposit
account.Deposit(-1000)

// Attempt negative withdrawal
canWithdrawNegative := account.Withdraw(-500)

// Attempt transfer to self
canSelfTransfer := account.Transfer(account, 1000)

// Final balance
finalBalance := account.Balance()

Output:
Balance after negative deposit attempt: 5000
Negative withdrawal attempt successful: false
Self-transfer attempt successful: false
Final balance: 5000

Explanation:
Negative deposits and withdrawals should not alter the balance or should fail as per design.
A self-transfer should ideally be handled gracefully (e.g., return false or no-op).
The balance remains unchanged at 5000 cents.

Constraints

  • The balance will be an integer and will not exceed 2^31 - 1 (max int32).
  • Deposit and withdrawal amounts will be integers.
  • Account holder names will be standard strings.
  • The implementation should be efficient and handle concurrent operations gracefully (though explicit concurrency handling is not required for this challenge, consider how your receiver choices might impact it).

Notes

  • Think carefully about the fundamental difference between value receivers and pointer receivers. A value receiver operates on a copy of the struct, while a pointer receiver operates on the original struct.
  • Consider which methods need to modify the original BankAccount state.
  • For the Transfer method, ensure that the withdrawal from the source account and the deposit to the destination account happen atomically (conceptually, though not necessarily with Go's concurrency primitives for this exercise). If either part fails, the entire transfer should be rolled back.
  • When handling edge cases like negative amounts, decide on the most sensible behavior (e.g., return an error, silently ignore, or panic if it indicates a severe logic flaw). For this challenge, returning false for failed operations and ensuring the balance remains unchanged is a good approach.
Loading editor...
go