Interview question
Implement an immutable USD money value object in Python
Builds a frozen dataclass around Decimal with explicit scale, validation, arithmetic, ordering, and value equality.
TL;DR
Builds a frozen dataclass around Decimal with explicit scale, validation, arithmetic, ordering, and value equality.
Decimal semantics, dataclass invariants, immutability, value equality, explicit rounding policy, and focused tests.
Practice the problem like a real interview: restate, reason, implement, and test.
Implement UsdMoney as an immutable Python value object. It must store exactly two decimal places, reject negative values and inputs that require rounding, support addition and ordering, and compare equivalent monetary inputs by value. Do not construct money from float or silently invent a rounding rule.
Move to the linked follow-up, next path step, prerequisite, or deeper variant.
Practice the next layer of the same subject.
UsdMoney.from_text("12.30") -> $12.30
$12.30 + $0.70 -> $13.00
UsdMoney.from_text("1.005") -> ValueError
UsdMoney.from_text("2.0") == ...( "2.00") -> True
Decimal, not binary floating point.Decimal.object.__setattr__ inside __post_init__ to store normalized state in the frozen instance.from dataclasses import dataclass
from decimal import Decimal
from functools import total_ordering
CENT = Decimal("0.01")
@total_ordering
@dataclass(frozen=True, slots=True)
class UsdMoney:
amount: Decimal
def __post_init__(self) -> None:
if not isinstance(self.amount, Decimal):
raise TypeError("amount must be Decimal")
normalized = self.amount.quantize(CENT)
if normalized != self.amount:
raise ValueError("amount must have at most two decimal places")
if normalized < 0:
raise ValueError("amount must be non-negative")
object.__setattr__(self, "amount", normalized)
@classmethod
def from_text(cls, value: str) -> "UsdMoney":
return cls(Decimal(value))
def __add__(self, other: "UsdMoney") -> "UsdMoney":
if not isinstance(other, UsdMoney):
return NotImplemented
return UsdMoney(self.amount + other.amount)
def __lt__(self, other: "UsdMoney") -> bool:
if not isinstance(other, UsdMoney):
return NotImplemented
return self.amount < other.amount
Construction, comparison, and addition are proportional to the number of decimal digits. Each value stores one normalized immutable Decimal.
2, 2.0, and 2.00 normalize to equal values.1.005 is rejected instead of rounded.Decimal with a float preserves binary floating-point noise.Spot a weak answer, missing edge case, or clearer explanation? Send it in.
Say thanks with a standalone, one-time $5 contribution. No account required.