Design Add, Overwrite, Undo, and Redo for Billing State
RedditSoftware Engineer · Technical ScreenLast updated
Design an in-memory billing account state manager that supports delta adjustments, absolute balance overwrites, and robust undo/redo capabilities.
Problem Statement
In financial and billing systems, balance updates must be strictly auditable, deterministic, and reversible. You are tasked with designing an in-memory billing ledger class, BillingAccount, that tracks customer balances in integer cents (to avoid floating-point inaccuracies).
The ledger must support incremental updates (add_delta), absolute adjustments (overwrite_balance), and full reversible history (undo and redo).
Implement the BillingAccount class with the following methods:
__init__(initial_balance_cents: int = 0): Initializes the account. RaisesValueErrorifinitial_balance_cents int: Adjusts current balance byamount_cents(can be positive or negative). If the resulting balance would be negative, the transaction must be rejected, the state unchanged, and aValueErrorraised. On success, any existing redo history is invalidated, and the new balance is returned.overwrite_balance(new_balance_cents: int) -> int: Explicitly sets the balance tonew_balance_cents. RaisesValueErrorifnew_balance_cents bool: Reverts the most recent mutation (add_deltaoroverwrite_balance). ReturnsTrueif an action was reverted, orFalseif there are no actions to undo.redo() -> bool: Reapplies the most recently undone action. ReturnsTrueif an action was reapplied, orFalseif there are no actions to redo.get_balance() -> int: Returns the current balance in integer cents.
All operations (add_delta, overwrite_balance, undo, redo, get_balance) must run in O(1) time.
Example
account = BillingAccount(100) # Initial balance: $1.00 (100 cents)
account.add_delta(50) # Balance: 150 cents
account.overwrite_balance(300)# Balance: 300 cents
account.add_delta(-100) # Balance: 200 cents
account.undo() # Undoes -100 delta -> Balance: 300 cents. Returns True.
account.undo() # Undoes overwrite -> Balance: 150 cents. Returns True.
account.redo() # Redoes overwrite -> Balance: 300 cents. Returns True.
account.add_delta(20) # Balance: 320 cents. Redo branch is now invalidated.
account.redo() # Returns False (redo history was cleared)
account.get_balance() # Returns 320
Constraints
0 <= balance_cents <= 10^12-10^10 <= amount_cents <= 10^10- At most
10^5operations will be called across any test run. - Balance cannot drop below
0at any point.
Hints
Hint 1
Use the Command pattern: store command records containing both the previous state and new state (or command-specific inverse metadata) to make undoing simple.
Hint 2
Maintain two stacks—`undo_stack` and `redo_stack`—and remember to clear `redo_stack` whenever a new forward operation (`add_delta` or `overwrite_balance`) succeeds.
Hint 3
Validate operations before mutating state or pushing records onto the history stack so invalid operations do not corrupt history.
Follow-up questions
- How would you scale this design to support concurrent transactions while guaranteeing linearizability and idempotency?
- If transaction history must persist across service restarts, how would you model the write-ahead log (WAL) and snapshotting strategy?
- How would you implement a memory cap on the undo stack while maintaining deterministic auditability?
Solution walkthrough is for members
Get the full walkthrough with reference code and complexity analysis, plus Kitty Coach at 300 messages a day. Hints, the code runner and community answers stay free.