AniUI Academy

Encapsulate state with the module pattern

mediumFunctions & Closures+250 XPPractice

Long before #private fields, the way to protect state was to keep it in a function's scope and return only the operations you wanted to allow. It is still the pattern behind most stores and SDK clients: the internals are unreachable, so every change goes through a method that can validate it.

Implement createAccount(initialBalance = 0). It returns an object with exactly four methods: deposit(amount) adds to the balance and returns the new balance; withdraw(amount) subtracts and returns the new balance; getBalance() returns the current balance; and getHistory() returns the recorded operations. The balance itself must not be an own property of the returned object, so its only own enumerable properties are those four methods. deposit and withdraw throw new Error("Invalid amount") when the amount is not greater than zero, and withdraw throws new Error("Insufficient funds") when the amount exceeds the balance; in both cases the balance is left unchanged and nothing is recorded. getHistory() returns a new array each time, holding one entry per successful operation in order, each of the form { type: "deposit", amount: 10 } or { type: "withdraw", amount: 5 } — mutating the returned array must not affect the account.

Arguments

initialBalancenumber
Starting balance. Defaults to 0.

Returns

{ deposit(amount): number; withdraw(amount): number; getBalance(): number; getHistory(): Array<{ type: 'deposit' | 'withdraw'; amount: number }> } — An account object exposing only the four methods — balance and history are private closures.

Examples

const account = createAccount(100);

account.deposit(50);  // 150
account.withdraw(30); // 120
account.getBalance(); // 120
const account = createAccount(20);
try { account.withdraw(50); } catch (e) { /* Insufficient funds */ }

account.getBalance(); // 20 — rejected operations leave state untouched
account.getHistory(); // [] — nothing was recorded

Validate before mutating; the account never ends up half-updated.

What it has to do

  • deposit and withdraw return the new balance and getBalance() agrees with them.
  • The returned object's only own enumerable properties are the four methods.
  • Throw Error("Invalid amount") for an amount that is not greater than zero.
  • Throw Error("Insufficient funds") when a withdrawal exceeds the balance, leaving the balance unchanged.
  • getHistory() returns a fresh array of { type, amount } entries for successful operations only.

Notes

  • getHistory() must build a new array (and new entry objects) every call — returning the internal array hands callers a way to rewrite history.
  • Use !(amount > 0) instead of amount <= 0: it correctly rejects NaN, which <= lets through.

Stuck? The javascript course covers everything this challenge needs.

Try it yourself
Loading playground...
5 tests · +250 XP

Discussion

Sign in to post
Sign in to join the conversation.Sign in

No comments yet. Be the first to say something.