oCamlCase
The challenge
Design a bank account model
Medium

Support deposits, withdrawals, and balance queries. The account should keep a complete history of all transactions, and balances should be computed from history rather than stored directly.

  • 01Define a transaction variant type with Deposit of float and Withdrawal of float constructors.
  • 02Define an account record with fields owner : string and history : transaction list.
  • 03Write deposit and withdraw. Each must return a new account value (immutable update via { acct with … }).
  • 04Write balance to compute the current balance by folding over history.
  • 05Write statement to return a (string * float) list with one entry per transaction, oldest first.

Starter code

bank.ml
type transaction =
  | Deposit    of float
  | Withdrawal of float

type account = {
  owner   : string;
  history : transaction list;
}

let create owner =
  { owner; history = [] }

let deposit amount acct =
  (* TODO *)

let withdraw amount acct =
  (* TODO *)

let balance acct =
  (* TODO: fold over acct.history *)
  0.0

let statement acct =
  (* TODO *)
  []

Hints

For deposit and withdraw, use the record update syntax { acct with history = … }. Prepend new transactions to the front of the list. This is O(1).

In balance, List.fold_left is the right tool. Start from 0.0 and adjust the accumulator on each transaction. Pattern match on the variant to know whether to add or subtract.

In statement, call List.rev acct.history first to get chronological order, then List.map each transaction to a label–amount tuple.

Key insight

The current balance is never stored. It is always derived from history. This makes past balances recomputable, which matters for auditing.

Compiler tip

If your match is missing a case, OCaml will warn you. Use that as a safety net, since warnings here are likely bugs.

+ Show solution try it yourself first
bank_solution.ml
type transaction =
  | Deposit    of float
  | Withdrawal of float

type account = {
  owner   : string;
  history : transaction list;
}

let create owner =
  { owner; history = [] }

let deposit amount acct =
  { acct with
    history = Deposit amount :: acct.history }

let withdraw amount acct =
  { acct with
    history = Withdrawal amount :: acct.history }

let balance acct =
  List.fold_left
    (fun acc tx ->
      match tx with
      | Deposit a    -> acc +. a
      | Withdrawal a -> acc -. a)
    0.0 acct.history

let statement acct =
  acct.history
  |> List.rev
  |> List.map (fun tx ->
    match tx with
    | Deposit a    -> ("Deposit",    a)
    | Withdrawal a -> ("Withdrawal", a))

(* Usage *)
let () =
  let acct =
    create "Alice"
    |> deposit  500.0
    |> deposit  250.0
    |> withdraw 100.0
  in
  Printf.printf "Balance: %.2f\n"
    (balance acct)
  (* Balance: 650.00 *)

Walkthrough

Immutability. Each call to deposit or withdraw returns a new account record with a transaction prepended to the history. The original is untouched. We use the pipe operator to chain updates cleanly.

fold_left. We start at 0.0 and walk the history list. For each entry, we add the amount on Deposit and subtract it on Withdrawal. Pattern matching on the variant is exhaustive and the compiler verifies this.

statement. Since we prepend transactions (newest first), List.rev puts them in chronological order. Then List.map converts each to a printable tuple.

Notice deposit and withdraw are structurally identical: both prepend a constructor to history. An advanced refactor could unify them into a single apply : transaction -> account -> account.

Going further

Once the basic model works:

  • Guard withdraw against overdrafts by returning Ok account or Error "Insufficient funds" using the result type.
  • Write balance_at : int -> account -> float that returns the balance after the first n transactions.
  • Add a Transfer of float * account variant that atomically moves funds between two accounts.
extension.ml
(* Safe withdrawal *)
let withdraw_safe amount acct =
  if balance acct < amount then
    Error "Insufficient funds"
  else
    Ok { acct with
      history = Withdrawal amount
                :: acct.history }

(* Balance after n transactions *)
let balance_at n acct =
  acct.history
  |> List.rev
  |> List.filteri (fun i _ -> i < n)
  |> List.fold_left
      (fun acc tx ->
        match tx with
        | Deposit a    -> acc +. a
        | Withdrawal a -> acc -. a)
      0.0