The Payments Engineer Playbook

The Payments Engineer Playbook

Source-Destination Pairs Need Imaginary Accounts to Work

Why Debits and Credits are the universal grammar of accounting

Alvaro Duran's avatar
Alvaro Duran
May 13, 2026
∙ Paid

Every ledger that uses source-destination pairs as a primitive ends up creating accounts that have no use other than making everything work.

Source-destination pairs are a Python dataclass, or a Java class, or a Go struct, that contains two mandatory fields that represent Accounts; one for the source of funds, another for the destination.

Engineers are tempted to use S-D pairs because there’s a rigidity in it that can be easily transferred to the type system. “If I can make this work”, they think, “I’ll be able to enforce double-entry safety in the compiler, rather than having to write lots of tests and be paranoid about it”.

Consider Nubank. There’s an interesting talk by Lucas Cavalcanti, now a Distinguished Engineer at Nubank, in which he claimed that double-entry accounting is “the perfect fit for functional programming”, and described how they build their ledger on top of an Entry schema that contains “a positive amount, a debit, and a credit”, and that “modeling it this way, by design, the sum of credits is equal to the sum of debits.”

Which is factually not true.

Why? Because, when I built a ledger for the first time, we used S-D pairs just like Nubank. And we used append-only, immutable records. And our Tech Lead couldn’t stop talking about Haskell and how what we were doing was as close to functional programming as you could get in Ruby.

And yet, we were losing track of a few cents on every transaction.

Engineers Do Not Get To Make Startup Mistakes When They Build Ledgers

Engineers Do Not Get To Make Startup Mistakes When They Build Ledgers

Alvaro Duran
·
September 11, 2024
Read full story

I’m Alvaro Duran, and this is The Payments Engineer Playbook. You’re already subscribed to free newsletters that “teach” you how to get a job as a software engineer.

But you don’t want to get a job; you already have one. What you want is to learn how to be great at your job. Especially as a payments engineer, where stakes are sky-high, and the margin for error is razor-thin.

In The Payments Engineer Playbook, we investigate the technology that transfers money. All to help you become a smarter, more skillful, and more successful payments engineer. And we do that by cutting off one sliver of it and extracting tactics from it.

In today’s article, we’re going to look at the fallacy of trying to enforce double-entry safety on the type system. I’ve already covered why thinking of accounting in terms of flows is a bad idea, and not using S-D pairs is its natural consequence. Enforcing a beginning and an end of funds is a misrepresentation of what accounting is really about, and ledgers that use S-D pairs as their primitive end up having to do all sort of workarounds to make the tests pass.

You used S-D pairs to get the compiler’s help, and then you make its help pointless.

Here’s what you can expect in today’s article:

  • The imaginary accounts needed to make S-D pairs work

  • Which ledgers could work with S-D pairs (not many)

  • The financial operations that S-D simply cannot model

Enough intro, let’s dive in.

User's avatar

Continue reading this post for free, courtesy of Alvaro Duran.

Or purchase a paid subscription.
© 2026 Alvaro Duran Barata · Privacy ∙ Terms ∙ Collection notice
Start your SubstackGet the app
Substack is the home for great culture