Katkat

Design a Cash Register with Inventory and Profit Tracking

SIG (Susquehanna)Software Engineer · Onsite

Last updated

Design an object-oriented cash register that manages multi-lot product inventory using FIFO accounting, executes atomic multi-item sales, and accurately tracks realized gross profit.

Problem Statement

Financial trading firms and retail engines alike require exact accounting and transactional guarantees. Design a cash register system that tracks product inventory across multiple purchase batches (lots), processes multi-product customer purchases atomically, and calculates realized gross profit using First-In, First-Out (FIFO) cost accounting.

All monetary values must be represented as integer cents to avoid floating-point rounding errors.

Requirements

Implement the CashRegister class with the following methods:

  • add_inventory(product_id: str, quantity: int, unit_cost_cents: int) -> None

    • Adds a new lot of quantity units of product_id purchased at unit_cost_cents per unit.
  • checkout(transaction_id: str, items: list[tuple[str, int, int]]) -> int

    • Processes a sale transaction. items is a list of tuples: (product_id, quantity, unit_price_cents).
    • Atomicity: Either all items in the basket are fulfilled or the transaction fails entirely. If any product has insufficient total inventory, the method must raise an InsufficientInventoryError (or return -1 if custom exceptions are not used), leaving all inventory lots completely unchanged.
    • FIFO Costing: Depletes stock starting from the oldest available lot for each product.
    • Idempotency: If a transaction with an already-processed transaction_id is submitted again, do not re-deduct inventory. Return the previously computed realized profit for that transaction.
    • Returns the realized gross profit for the transaction: total_revenue - total_cost_of_goods_sold.
  • get_total_realized_profit() -> int

    • Returns the cumulative realized gross profit across all successful checkouts in cents.

Example

register = CashRegister()

# Buy 10 widgets at $1.00 each, then 10 widgets at $1.50 each
register.add_inventory("widget", 10, 100)
register.add_inventory("widget", 10, 150)

# Sell 15 widgets at $2.00 each in transaction "tx-001"
# Revenue: 15 * 200 = 3000 cents
# COGS (FIFO): (10 * 100) + (5 * 150) = 1000 + 750 = 1750 cents
# Realized Profit: 3000 - 1750 = 1250 cents ($12.50)
profit1 = register.checkout("tx-001", [("widget", 15, 200)])
assert profit1 == 1250

# Idempotent retry returns the same profit without deducting stock
assert register.checkout("tx-001", [("widget", 15, 200)]) == 1250

# Remaining widget inventory: 5 units from the second lot (cost 150)
# Attempting to sell 10 widgets should fail atomically
try:
    register.checkout("tx-002", [("widget", 10, 200)])
except InsufficientInventoryError:
    pass

# Cumulative profit
assert register.get_total_realized_profit() == 1250

Constraints

  • 1 <= quantity <= 10^5
  • 0 <= unit_cost_cents, unit_price_cents <= 10^7
  • Up to 10^4 calls to add_inventory and checkout.
  • product_id and transaction_id are non-empty alphanumeric strings.

Hints

Hint 1

Always represent currency as integer cents (or a fixed-point decimal) to prevent floating-point inaccuracies.

Hint 2

Use a queue (e.g., `collections.deque`) of lots per product to consume inventory in First-In, First-Out order.

Hint 3

To guarantee atomicity, verify that available stock across all requested products is sufficient before mutating any lot queues.

Follow-up questions

  • How would you ensure thread-safety for concurrent checkout requests without locking the entire register with a single coarse-grained lock?
  • How should the system handle customer returns or refunds under FIFO accounting: should returned items be restored to their original lots or added as new lots?
  • How would you store an immutable audit log of every sale such that historical inventory state and profit calculations can be reconstructed from scratch?