The Hard Rules of Financial Engineering
Why standard web development patterns fail when money is the primary primitive, and how to build systems that actually balance.
In standard web development, a dropped log, a slightly delayed background job, or a minor rounding error is usually just a low-priority ticket in the backlog. In financial engineering, those same minor hiccups are regulatory violations, direct balance sheet losses, or audit failures.
According to the World Economic Forum (WEF) in its Future of Jobs Report 2025, fintech engineering is projected to be the second fastest-growing job category by 2030. Yet, many developers entering this domain try to apply standard CRUD patterns to systems where money is the primary focus. The result is almost always catastrophic. Building trustworthy financial systems requires throwing out eventual consistency, silent error handling, and standard floating-point math in favor of strict, defensive architectural patterns.
The Three Pillars of Financial Integrity
To build software that handles money reliably, systems must adhere to three core principles: zero invented data, zero lost data, and zero trust.
No Invented Data
Money cannot be created out of thin air. In practice, this means a system cannot tolerate duplicate transactions or arbitrary balance updates. If a user clicks a payment button twice, the system must guarantee that the second request is safely discarded or resolved to the original transaction. This is enforced through strict idempotency keys, deduplication layers at the database level, and continuous reconciliation engines that verify internal balances against external reality.
No Lost Data
Every single state change, ledger entry, and transaction must be tracked, immutable, and persisted. Financial systems rely heavily on event sourcing and comprehensive audit trails. If a balance changes, there must be a corresponding ledger entry explaining exactly why. At-least-once delivery guarantees are mandatory when communicating between services, ensuring that no message is dropped in transit.
No Trust
Never assume that external APIs, third-party payment gateways, or even internal microservices are behaving correctly. Webhooks must be cryptographically verified, data must be cross-checked across multiple sources, and the system must fail loudly and immediately when any invariant is broken. A silent failure in a financial pipeline is a liability; an immediate crash is a safety feature.
The Data Modeling Trap: Representing Money Without Floats
The most fundamental decision in a financial system is how to represent currency. Using standard floating-point numbers (like float or double in most programming languages) is a fatal mistake. Floating-point arithmetic introduces unpredictable precision losses due to how binary systems represent base-10 decimals.
Developers generally choose between three reliable alternatives depending on the specific use case:
- Minor-Units Precision: This approach stores monetary values as integers representing the smallest currency unit. For example, USD or EUR is stored in cents, meaning $12.34 becomes
1234. This avoids decimal math entirely. However, developers must not assume every currency uses two decimal places. The ISO 4217 standard defines the correct exponent for each fiat currency. Crypto assets use a similar minor-unit pattern (such as satoshis for BTC or wei for ETH), but they often require 18 digits of precision. These massive numbers easily overflow standard 64-bit integers, requiring arbitrary-width integer libraries to prevent corruption. - Arbitrary Precision: For intermediate calculations like foreign exchange (FX) rates, interest calculations, or complex pricing matrices, minor-unit integers are not flexible enough. In these scenarios, developers use arbitrary-precision libraries like Java's BigDecimal. This allows explicit control over where and how rounding occurs, though it comes with a performance penalty.
- Rational Numbers: When absolutely zero precision loss can be tolerated across chained divisions, rational numbers (storing values as a numerator and denominator) offer the most mathematically rigorous approach. However, they are slow, require custom data types, and eventually must be converted back to standard formats at system boundaries.
The Serialization Gotcha
Even if a system uses integers or arbitrary-precision types internally, the data can still be corrupted at the boundary. A bare JSON number (e.g., {"amount": 12.34}) is parsed as an IEEE-754 double by most standard JSON parsers. To prevent this silent loss of precision, money must always be serialized as a string (e.g., "12.34") or as an explicit integer in its minor unit (e.g., 1234) when transmitted over HTTP or RPC boundaries.
Rounding Strategies and the Broken Sum Problem
Rounding is a business decision, not just a mathematical one. Any operation that involves division, currency conversion, or fee application will eventually require rounding.
To maintain system integrity, rounding should be delayed as long as possible. The longer full precision is maintained during intermediate calculations, the more accurate the final result will be. Rounding should only occur at explicit boundaries, such as right before displaying a value to a user or persisting it to a ledger.
Furthermore, rounding breaks sums. If a $10.00 charge is split equally among three users, a naive division results in $3.33 per person. Summing those rounded values yields $9.99, leaving a residual penny unaccounted for. In financial systems, that penny cannot simply disappear. The system must explicitly track residuals, often routing them to a dedicated rounding or variance account to ensure the ledger remains perfectly balanced.
Developer Angle: Designing a Safe Money Type
To prevent bugs from slipping into production, developers should encapsulate monetary values into a dedicated Money value object. This object should enforce two strict rules: it must couple the amount with its currency, and it must explicitly prohibit cross-currency arithmetic.
Here is a TypeScript implementation demonstrating how to enforce these constraints, validate currency codes at the system boundary, and prevent serialization issues:
export class Money {
private constructor(
public readonly amountInMinorUnit: bigint,
public readonly currency: string
) {}
// A controlled set of supported currencies
private static readonly SUPPORTED_CURRENCIES = new Set(['USD', 'EUR', 'GBP']);
public static fromMinorUnit(amount: bigint, currency: string): Money {
const upperCurrency = currency.toUpperCase();
if (!this.SUPPORTED_CURRENCIES.has(upperCurrency)) {
throw new Error(`Unsupported or invalid currency: ${currency}`);
}
return new Money(amount, upperCurrency);
}
public add(other: Money): Money {
if (this.currency !== other.currency) {
throw new Error(
`Cross-currency arithmetic forbidden: Cannot add ${this.currency} and ${other.currency}`
);
}
return new Money(this.amountInMinorUnit + other.amountInMinorUnit, this.currency);
}
// Safe serialization to prevent floating-point parsing at the API edge
public toJSON() {
return {
amount: this.amountInMinorUnit.toString(),
currency: this.currency
};
}
}
When integrating with external payment networks or internal ledger databases, this pattern ensures that invalid operations fail at compile-time or early runtime, rather than corrupting database records.
The Reality of Financial Systems
Building software in regulated financial domains is not about velocity; it is about predictability. The patterns described here require more boilerplate, stricter validation, and a willingness to let processes fail explicitly rather than recover silently. However, when the alternative is a broken ledger and a regulatory audit, defensive engineering is the only viable path forward.
Sources & further reading
- Fintech Engineering Handbook — w.pitula.me
- FinTech for Managers : A Handbook – Sybgen Learning — sybgenlearning.com
- FinTech Engineers: The Architects of Tomorrow's Financial Services – And How Duke's MEng in FinTech Can Pave the Way | Duke Engineering Master's Programs — masters.pratt.duke.edu
Lenn writes about cloud platforms, Kubernetes internals, and the infrastructure decisions that quietly make or break engineering organizations. Based in Berlin's vibrant tech scene, they have a talent for turning dense platform-engineering topics into prose that people actually finish reading.
Discussion 2
i've seen this play out in my own work, where a tiny rounding error in a financial model can have huge implications - it's crazy how something as simple as a misplaced decimal can become a major audit issue, definitely need to rethink our approach to building these systems
okay this is actually huge - the part about throwing out eventual consistency really resonates, i've seen so many fintech projects go sideways because they tried to apply standard crud patterns to financial systems