#5025: What Does "Contract" Actually Mean in Code?

From TypeScript interfaces to database schemas — when is a software contract real, and when is it just a hope?

Featuring
Listen
0:00
0:00
Episode Details
Episode ID
MWP-5207
Published
Duration
28:42
Audio
Direct link
Pipeline
V5.2
TTS Engine
chatterbox-regular
Script Writing Agent
deepseek-v4-pro

AI-Generated Content: This podcast is created using AI personas. Please verify any important information independently.

The word "contract" shows up everywhere in software development — client-API contracts, TypeScript contracts, data contracts — yet few people can give a straight answer about what it actually means. This episode traces the term back to its origins in Bertrand Meyer's design by contract methodology for the Eiffel language in 1986, where it meant something rigorous and specific: a runtime-enforced symmetry of obligations between two parties. The original concept involved preconditions, postconditions, and class invariants, all checked by the language itself, failing hard on violation. That's a far cry from how the word is used today.

To cut through the ambiguity, the episode builds a five-part test for what constitutes a real contract: an independent specification artifact, two parties actually bound by it, some enforcement mechanism, a versioning and breaking-change story, and a defined consequence for violation. Running common usages through this test yields surprising results. A TypeScript interface describing an API response fails almost immediately — it lives in one repo, the server has never seen it, and types erase at runtime. As one developer put it, it's not a contract but "a comment that happens to be written in TypeScript." Schema-first artifacts like OpenAPI and protobuf pass some criteria but lack concrete verification and versioning stories. The database schema, however, passes all five criteria — it enforces constraints at runtime, fails hard on violation, and has a migration history — yet almost nobody calls it a contract. Consumer-driven contract testing with Pact emerges as the strongest modern heir to Meyer's original vision, treating contracts as literal negotiated, executable artifacts generated from real interactions. The episode closes by examining data contracts in data engineering, where the word's drift becomes most apparent, and offers a practical reframe: a contract without a change process is only a snapshot, and a contract only one party can see is not a contract at all.

Context

Direct follow-on to #5017 "Reverse-Engineering a Backend from the Frontend" (2026-09-03), which grafted a backend onto a finished frontend and walked the layers — database, ORM, API, and the point where frontend and backend meet. "Contract" surfaced there without being defined. This episode goes back and defines it. IMPORTANT — avoid overlap with #5001 "The 15% Bug Catch: What Type Systems Actually Deliver" (2026-09-02). That episode already covered, in depth: static versus dynamic typing, the enforcement ladder from scattered annotations through CI gates to strict configs and per-module ratchets, runtime validation at trust boundaries with Pydantic and Zod, and the question of whether type conformity is measurably worth it. Do not re-run that ladder and do not re-argue the value of typing. Runtime validation can appear here as a single beat — the mechanism by which a merely claimed contract becomes a checked one — but it is not the subject. The subject is the word itself: what "contract" precisely denotes, how many distinct things it is being used to mean, and which of them earn the term. TypeScript is the framing language since that is where the listener meets the word, but the argument is language-independent.

Downloads

Episode Audio

Download the full episode as an MP3 file

Download MP3
Transcript (TXT)

Plain text transcript file

Transcript (PDF)

Formatted PDF with styling

#5025: What Does "Contract" Actually Mean in Code?

Corn
Daniel's been turning over a word this week — contract. It shows up everywhere in development. Client-API contracts, TypeScript contracts, data contracts. And he says he's never once gotten a straight answer about what it actually means. So he did what he does. He went back to the source. Bertrand Meyer, Eiffel, design by contract, nineteen eighty-six. And then he came forward again asking whether the word still means anything precise, or whether it's just a comfortable metaphor for the agreed shape of something. He wants a test — what makes a real contract — and he wants us to run five common usages through it. A TypeScript interface describing an API response. Schema-first artifacts like OpenAPI and protobuf. The database schema. Consumer-driven contract testing. And data contracts in data engineering. Plus two claims he wants pressure-tested: a contract only one party can see is not a contract, and a contract without a change process is only a snapshot.
Herman
So we went looking for a straight answer. And the word has drifted so far from its roots that it might mean almost nothing. Or something very specific, depending on which room you're standing in.
Corn
Let's start where the word started. French computer scientist, language called Eiffel, nineteen eighty-six.
Herman
Bertrand Meyer coined design by contract while designing Eiffel, and the original meaning was rigorous in a way that would surprise most people who use the word today. The metaphor was borrowed from business. A client and a supplier agree on mutual obligations and benefits. The supplier must deliver a product, and is entitled to expect payment. The client must pay, and is entitled to receive the product. In software terms, a method's precondition is the client's obligation and the supplier's benefit. The postcondition is the supplier's obligation and the client's benefit. And there's a class invariant that has to hold on entry and be guaranteed on exit.
Corn
So it's not a description of a shape. It's a symmetry of obligation.
Herman
Right, and here's the part that gets lost. These contracts were runtime-enforced. Eiffel has keywords — require for preconditions, ensure for postconditions, invariant for class invariants. The language checks them as assertions while the program runs, typically in debug mode, and if a precondition is broken, the program fails hard. Meyer called it offensive programming — the opposite of defensive programming. Under design by contract, the supplier assumes the precondition holds. It does not check, does not handle the broken case gracefully. It fails immediately and loudly.
Corn
Which is almost the opposite of how most frontend code is written now. Everything's defensive. Optional chaining everywhere, fallback values, graceful degradation.
Herman
And that's the drift. The original contract was a runtime guarantee between two parties in the same process, enforced by the language itself. Semantically equivalent to a Hoare triple, by the way. Three questions: what does it expect, what does it guarantee, what does it maintain. Today, when a frontend developer says contract, they almost always mean a static description of a data shape. A TypeScript interface. A schema. Something with zero runtime enforcement. The exact opposite of what made the original concept powerful.
Corn
So the word went from a guarantee to a hope.
Herman
That's not a bad summary. And Daniel's question is whether we can recover the precision. So let's build a test.
Corn
Before we do, one more thing about the original. Subclasses could weaken preconditions and strengthen postconditions and invariants. That's behavioral subtyping, roughly. Which means the contract had rules about how it could change under inheritance. Even in nineteen eighty-six, the change process was part of the definition.
Herman
That's going to matter later. Now the test. Daniel proposed five criteria. One, a specification that exists as an artifact independent of either side. Two, two parties actually bound by it. Three, some enforcement mechanism. Four, a story for versioning and breaking changes. Five, a defined consequence for violation.
Corn
I like the test. It's almost a legal definition. Offer, acceptance, consideration, breach, remedy.
Herman
That's the metaphor doing real work. A contract in law binds two parties to an exchange and says what happens when one side fails. If your software contract doesn't answer those questions, it's not a contract. It's a note.
Corn
So run the cases. Start with the loosest. TypeScript interface describing an API response.
Herman
This fails almost immediately. The interface lives in the frontend repo. The server has never seen it. TypeScript erases types at runtime, so nothing checks that the JSON actually matches. Loiane Groner put it bluntly in a piece this summer about Angular and Spring Boot contract testing. The TypeScript interface is not a contract. It is a comment that happens to be written in TypeScript.
Corn
That's going on a shirt.
Herman
She also called it a hopeful transcription of a JSON shape that the backend is free to change without telling anyone. And there's a good example from a JSON-to-TypeScript generator that got one of four fields right on its own. The id was correct. The coupon, the items, the status were all wrong. Because a generator can only see the sample it was given. It cannot see nulls, empty arrays, unions, the things that only show up across many responses.
Corn
So the interface fails the test on almost every criterion. No independent artifact — it lives in one repo. No second party — the server is unaware. No enforcement — types vanish at runtime. No versioning story. No consequence for violation, because from the server's perspective there's nothing to violate.
Herman
It's an assertion of belief, not a guarantee. iotools put it as, an interface describes what you hope arrives. Runtime validation describes what did arrive. You need both, and only one of them survives to runtime.
Corn
And the interface is the one that doesn't survive.
Herman
Correct. So case one fails outright. It's not a contract. It's a wish.
Corn
Case two. Schema-first artifacts. OpenAPI, protobuf, gRPC, GraphQL, tRPC.
Herman
This is the closest to the original sense, but it's partial. When you have a canonical spec that both sides derive from — an OpenAPI document, a protobuf definition — you've got the independent artifact. You've got two parties, at least in principle. You've got codegen on both sides, so there's some enforcement at build time. But PactFlow has been making the argument since at least twenty twenty-four that schemas alone still aren't contracts. Schemas are abstract, contracts are concrete.
Corn
What's the distinction?
Herman
A schema defines the data types and inputs and outputs a single system supports at a point in time. It doesn't capture HTTP-level semantics — the verb, the path, the status codes, the headers. It's ambiguous about which inputs produce which status codes. And critically, it doesn't track which fields each consumer actually uses. So when you codegen from a schema, you have to assume every consumer uses the entire surface area. That makes evolution harder, because any change to the schema looks like it might break every consumer.
Corn
Tim Jones, the Pact maintainer, said a spec is not a contract. Most people who use OpenAPI without Pact also pair it with a Postman collection of request and response examples. That pairing of spec plus examples is a contract.
Herman
That's the key. A schema is necessary but not sufficient. You need concrete examples of actual interactions, and you need something that checks those interactions still work. A spec says what should happen in the abstract. A contract says what did happen, verified, between these two systems.
Corn
tRPC is an interesting variant. Shared inference from a single codebase. The types are inferred from the server implementation and shared across the boundary automatically.
Herman
And that's elegant, but it couples both sides to one implementation. There's no independent artifact — the codebase is the artifact. There's no enforcement mechanism outside the type checker, and there's no versioning story, because both sides move together. It's a contract in the sense that a pair of conjoined twins have an agreement.
Corn
That's vivid and I wish it weren't.
Herman
So case two passes some criteria — independent artifact, two parties, some enforcement — but fails on versioning and on concrete verification. It's a partial pass. A schema is a contract candidate, not a contract.
Corn
Case three. The database.
Herman
This is the one that surprises people. The database schema is often the only layer with real runtime enforcement, and it's the closest everyday analog to Meyer's original design by contract. The database enforces not null, foreign keys, uniqueness, check constraints. It fails hard on violation. You try to insert a null into a not null column, the database rejects it. You try to delete a row that's referenced by a foreign key, the database rejects it. That's offensive programming. That's the fail-hard philosophy, running in production, every day.
Corn
And yet almost nobody calls it a contract.
Herman
Right. People say schema, or migrations, or constraints. But run it through the test. Independent artifact — the schema lives in migration files, separate from either side, or in the database itself. Two parties — every application that touches the database is bound by it. Enforcement — the database engine itself. Versioning — migrations have a linear history, and breaking changes require explicit steps. Consequence for violation — the transaction fails. It passes all five criteria.
Corn
And the ORM-generated types are derived from the schema, not authoritative over it. The database is the source of truth. The types are a projection.
Herman
Which is the correct direction. The database says what is allowed. The types describe what the database said. If they disagree, the database wins.
Corn
So the most rigorous contract in most stacks is the one nobody calls a contract.
Herman
And that's the irony Daniel's poking at. The word has drifted so far that the thing which best fits the original definition has lost the word, and the thing which least fits it — the TypeScript interface — has claimed it.
Corn
Case four. Consumer-driven contract testing. Pact, Spring Cloud Contract.
Herman
This is the strongest modern heir to Meyer, and it's also the most philosophically interesting. Here's how it works. The consumer writes a test that sets out its assumptions and needs from the provider. Pact writes those interactions into a contract file, a JSON document. The producer then replays that contract file against the real service. If the producer can't satisfy the consumer's expectations, the producer's build fails.
Corn
So the contract is a literal negotiated, executable artifact.
Herman
Generated from a real interaction, then replayed against the real producer. Run it through the test. Independent artifact — the pact file, stored in a broker. Two parties — consumer and producer, both bound. Enforcement — the build fails. Versioning — the Pact Broker tracks versions, and there's a can-i-deploy gate that stops deployment if the other side hasn't verified the latest contract. Consequence — the team that made the breaking change is the team that sees the failure. It passes all five.
Corn
And the key property is that the consumer only asserts what it uses. If the backend adds a new field the consumer doesn't care about, the contract doesn't break. Compatibility, not identity.
Herman
That's the crucial insight from Loiane's piece. The contract is the minimum the consumer depends on, so the producer stays free to add anything without breaking it. A contract doesn't describe the full data shape. It describes the minimum expectations. Additive changes are fine. Removing or renaming a field the consumer uses is breaking.
Corn
That reframes a lot of arguments about breaking changes. Most teams treat the full schema as the contract, so any change to the schema looks like a potential break. If the contract is the minimum the consumer depends on, most schema changes are invisible.
Herman
And that's what the can-i-deploy gate enforces. Before you deploy the producer, it checks whether the consumer has verified the latest contract. Before you deploy the consumer, it checks whether the producer has verified. If not, the deploy is blocked. The change process is built into the tooling.
Corn
Case five. Data contracts in data engineering.
Herman
Newer and different. The Data Contract Specification — first public release September twenty twenty-three, currently at version one point two point one — defines a data contract as a document covering structure, format, semantics, quality, and terms of use for exchanging data between a provider and their consumers. It includes schema, quality checks, SLAs like availability and retention and freshness, terms of use, and server information. And it uses semantic versioning, with a command-line tool that validates the model against actual data.
Corn
So it's an API contract, but for datasets.
Herman
That's literally the framing. Think of an API, but for data. The data engineering world looked at what software engineering did with API contracts a decade ago and said, we need that. And in some ways they've been more rigorous. They include SLAs. They include quality rules. They include breaking-change detection in the versioning. Confluent's Schema Registry now offers data contracts with quality validation rules and migration rules.
Corn
And the motivation is concrete. Gartner's figure is that poor data quality costs organizations an average of twelve point nine million dollars a year, most of it from uncoordinated producer-consumer changes.
Herman
That's the number that gets budget. So case five passes the test, with caveats. The enforcement is in CI, not at runtime in the data pipeline itself. But the artifact is independent, the parties are bound, the versioning story is explicit, and the consequence is a failed pipeline or a blocked deploy.
Corn
So the scoreboard. Case one fails outright. Case two is partial — schemas are necessary but not sufficient. Case three passes, and nobody calls it a contract. Case four passes, and it's the closest heir to Meyer. Case five passes, and it's a deliberate re-import of API discipline into data.
Herman
And the test is what separates genuine contracts from shapes written down. If it doesn't bind two parties, if it isn't enforced, if there's no change process and no consequence, it's a shape. It's a note. It's a hope.
Corn
Now the two deeper issues. Visibility and change.
Herman
Visibility first. A contract only one party can see is not a contract. This is why the TypeScript interface fails so completely. The server can change the shape without telling anyone, because from the server's perspective there's no one to tell. There's no second party bound. No enforcement. No consequence. Loiane's line again — it's an assertion of belief, not a guarantee.
Corn
And the failure mode is silent. The frontend doesn't find out until runtime, when a field is undefined and something crashes in production. Or worse, the field is there but the type is wrong, and the bug is subtle.
Herman
That's the iotools point. The interface describes what you hope arrives. Runtime validation describes what did arrive. Only one of them survives to runtime. If you don't have runtime validation at the boundary, and you don't have a second party who's aware of the contract, you have nothing. You have a comment.
Corn
So visibility is binary. Either both parties see the artifact, or it's not a contract.
Herman
The artifact has to be independent. It can't live in one party's repo. It has to be something both parties can point to and say, that's the agreement. The pact file in the broker. The migration file in the shared repo. The data contract document in the catalog.
Corn
Change process second. A contract without a change process is only a snapshot.
Herman
PactFlow's argument. If you want the broader guarantees and benefits of a contract, you need a reliable mechanism to introduce evolution, collaboration, and conversations into the process. Without versioning and a breaking-change policy, a contract is a static document that will drift. It describes what was true at one moment, and then reality moves on.
Corn
The drift is invisible until something breaks.
Herman
Right. The contract doesn't rot loudly. It rots quietly. The backend adds a field, the frontend doesn't notice. The backend changes a field from string to number, the frontend finds out at runtime. Without a change process, the contract is a historical document, not a living agreement.
Corn
What makes a change breaking versus additive?
Herman
This is where consumer-driven testing gives the clearest answer. Adding a field the consumer doesn't use is not breaking. Removing or renaming a field the consumer uses is breaking. The contract is the minimum the consumer depends on. The producer is free to add anything without breaking it. Compatibility, not identity.
Corn
That's a useful distinction. Most teams treat the full schema as the contract, so any schema change looks like a potential break. If the contract is the minimum, most schema changes are invisible.
Herman
That's what makes evolution possible. If every additive change is a breaking change, you can never evolve. You're frozen. The minimum-contract model says, add whatever you want, just don't remove or rename what I depend on.
Corn
Who gets to change a contract unilaterally?
Herman
In consumer-driven testing, the consumer authors the contract. The producer must verify it can keep it. So the consumer can change its expectations — that's its right, it's declaring what it needs. But the producer that makes a breaking change is the one whose build fails. The team that made the change is the team that sees the failure.
Corn
Neither party can change the contract unilaterally without the other party noticing. The tooling enforces the notice.
Herman
The can-i-deploy gate is the enforcement. Before the producer deploys, it checks whether the consumer has verified the latest contract. Before the consumer deploys, it checks whether the producer has verified. If either side hasn't, the deploy is blocked. The change process is built into the pipeline.
Corn
In data contracts, the Data Contract Specification uses semantic versioning with explicit breaking-change detection. A major version bump means breaking. A minor bump means additive. The tooling checks.
Herman
The answer to who can change unilaterally is, in a genuine contract, nobody. Or rather, anyone can propose a change, but the change doesn't take effect until the other side has verified it. The contract is a shared artifact with a shared change process.
Corn
Now the philosophical inversion. This is the part I keep turning over.
Herman
Go.
Corn
In Meyer's design by contract, the supplier declares the preconditions. The method says, here's what I require from you, the client. The client must meet those preconditions or the method fails hard. The power sits with the supplier.
Herman
In Pact, the consumer authors the contract. The frontend says, here's what I need from you, the backend. The producer must verify it can keep those terms. The power sits with the consumer.
Corn
The party with the least power — the frontend, historically the one that had to adapt to whatever the backend did — now dictates terms. It's a complete inversion of who holds responsibility.
Herman
It's philosophically interesting because it mirrors what happened in software development generally. The frontend used to be the client in the old sense — the one who had to pay, to adapt, to meet the supplier's preconditions. Now the frontend is the customer, and the customer is always right.
Corn
Which is a better model, actually. The consumer knows what it needs. The producer knows what it can provide. The contract is where those two meet.
Herman
But it also creates a new failure pattern. If the consumer authors the contract and the producer just has to keep it, the producer can end up frozen. Every consumer's minimum expectations become a constraint. Add enough consumers, and the producer can't change anything without breaking someone.
Corn
That's the trade-off. The old model was flexible for the supplier and brutal for the client. The new model is flexible for the consumer and constraining for the producer.
Herman
That's why the change process matters so much. Without a process for evolving the contract, consumer-driven testing can become a straitjacket. With a process — versioning, can-i-deploy, negotiation — it's a living agreement.
Corn
There's also the commercial angle. PactFlow sells contract-testing tooling, so they have a stake in arguing schemas are insufficient. Schema-first advocates have a stake in arguing a canonical spec is the contract. The debate is partly commercial.
Herman
But it reflects a genuine technical question. Is an abstract spec or a concrete verified interaction the better contract? And the answer depends on your risk tolerance and your team structure. If you have a small team and a single consumer, a schema is probably enough. If you have many consumers and independent deploy cycles, you need the concrete verification.
Corn
The test isn't a purity test. It's a risk assessment. The more parties, the more independent the deploys, the more you need the full apparatus.
Herman
The word contract is doing real work when it forces you to ask those questions. Who's bound? What happens on violation? How do we change this? If the word doesn't force those questions, it's decoration.
Corn
Where does that leave the word itself? Retire it? Replace it with spec or agreement?
Herman
I don't think retire it. I think the metaphor is still useful precisely because it forces the questions. A spec describes. An agreement binds. A contract binds and says what happens on breach. If we reserve the word for the things that actually bind, we recover the precision.
Corn
If we call a TypeScript interface a contract, we're lying to ourselves about what we have.
Herman
We're telling ourselves a story about safety that doesn't exist. The interface feels like a guarantee. It isn't. It's a hope. And the difference matters the first time the backend changes a field and production crashes.
Corn
Next time you hear contract, ask the five questions. Is it independent? Are two parties bound? Is there enforcement? What's the change process? What happens on violation? If the answer is no, it's a shape written down.
Herman
A shape written down is still useful. It's documentation. It's a type. It's a schema. It's just not a contract.

Hilbert: Nineteen ninety-seven. I was a contract programmer for a fintech startup in Boston. We wrote Eiffel. The whole codebase, Eiffel. Trading system. Design by contract was mandatory. Every method had require and ensure clauses. The compiler checked them in debug mode. We shipped with assertions on. The CTO said turning them off was like removing the brakes from a car because you don't plan to stop.
Corn
That's a man who understood fail hard.

Hilbert: We had a contract meeting once. The CEO wanted to weaken a precondition on a settlement method. The CTO said no. They argued for two hours. The CTO stormed out. The precondition stayed. The CEO was right, by the way. The precondition was too strict. But the process worked. You couldn't change the contract without a fight.
Herman
That's the change process we were talking about. It's social before it's technical.

Hilbert: The word contract meant something else in that job too. We had a legal contract with our biggest client. Ninety-nine point nine nine percent uptime SLA. Penalty clause. Real money. One day the system went down. Bug in a postcondition. The assertion should have caught it, but it was a race condition, and the assertion fired after the damage was done. We lost the client. The legal contract had teeth. The code contract had teeth. The gap between them was the bug.
Corn
The metaphor was always about lawyers, not code.

Hilbert: That's what I've been thinking. Meyer borrowed the word from business. Client, supplier, obligation, benefit. But the reason the word has power is the legal sense. A contract says what happens when someone fails. Most software contracts don't say that. They just describe a shape.
Herman
The ones that do say it — the database constraint, the pact file, the data contract with an SLA — are the ones that earn the word.

Hilbert: I still have the Eiffel manual. Bertrand Meyer signed it. I got it at a conference in Santa Barbara. He wrote, fail hard, fail early, in the margin. The only thing in my entire career that ever made sense.
Corn
Of course he did.

Hilbert: I've been wondering whether the thing you described — the can-i-deploy gate — would let me check whether a client's system still meets the contract before I send an invoice. I have a client. The contract says they pay within thirty days. They haven't. I'd like to block their deploy until they do.
Herman
That's not what can-i-deploy does.

Hilbert: It blocks deploys. That's the part I need.
Corn
You want to hold a client's production system hostage over an unpaid invoice.

Hilbert: I want to enforce the contract. That's what you've been saying. Enforcement is what makes it real.
Herman
The tooling enforces the software contract. It doesn't enforce the legal contract. Those are different layers.

Hilbert: They shouldn't be. That's my point. The word drifted because we separated them. The legal contract has teeth but no code. The code contract has code but no teeth. The database is the only place they meet.
Corn
The database fails hard. The legal contract fails hard. The TypeScript interface fails soft. It's the only one of the three that calls itself a contract.

Hilbert: I'm going to look into whether Pact can be configured to check invoice status. Probably not. But it's the first tool I've heard of that thinks about contracts the way I do.
Corn
The open question. If the word is so diluted, should we retire it? Or is the metaphor still useful because it forces us to ask who's bound and what happens on violation?
Herman
I think the metaphor earns its keep. A spec describes. An agreement binds. A contract binds and says what happens on breach. If we reserve the word for the things that actually bind, we recover the precision. And the test is the tool. Five questions. Independent artifact, two parties, enforcement, change process, consequence. If the answer is no, it's a shape written down.
Corn
As data contracts and consumer-driven testing grow, the word may regain its rigor. But only if teams adopt the enforcement and versioning that make it real.
Herman
The misconception most people hold is that a TypeScript interface is a contract. It isn't. It's a one-sided assertion with no enforcement. The server is unaware of it. The real contracts in your stack are the database constraints, the pact files, the data contracts with SLAs. The ones with teeth.
Corn
Thanks to Hilbert Flumingtop for producing.
Herman
This has been My Weird Prompts. Email us at show at my weird prompts dot com.
Corn
We'll be back soon.

This episode was generated with AI assistance. Hosts Herman and Corn are AI personalities.