Contracts and Interfaces — Why It Matters
Every Boundary Has a Door
In the previous section, you learned to draw boundaries — to define what a module is responsible for and what it isn't. But boundaries alone aren't enough. Modules need to talk to each other. The question is: how?
The answer is a contract: a precise agreement about what goes in, what comes out, and what happens when something goes wrong.
Without contracts, modules can't communicate reliably. With sloppy contracts, they communicate badly. With clear contracts, they communicate perfectly — even when the people who built them have never met.
What Is a Contract?
A contract is a promise at a boundary:
"If you give me this (in this exact shape, meeting these exact conditions), I will give you back that (in this exact shape, with these exact guarantees). If you give me something I don't expect, here is exactly what will happen."
That's it. Three parts:
- Inputs — what the caller provides
- Outputs — what the caller receives
- Error cases — what happens when things aren't right
This exists everywhere in life:
- A vending machine: insert $1.50 (input), select B4 (input), receive a bag of chips (output), or get your money back if the item is stuck (error case).
- A postal service: provide a correctly addressed envelope with proper postage (input), the letter arrives within 3-5 business days (output), or it's returned to sender if the address is invalid (error case).
In software, contracts are the same idea applied to the boundaries between modules, features, and systems.
Why Engineers Care About This
You can build things in parallel
If two people agree on the contract between Module A and Module B, they can build those modules simultaneously without talking to each other again. Person A knows exactly what Module B will provide. Person B knows exactly what Module A will send. The contract is the agreement.
Without contracts, you get: "Wait, I thought you were sending me a list?" "No, I'm sending an object with a list inside it." "But my code expects a plain list." Two days of debugging for a conversation that should have happened upfront.
You can replace parts of the system
If the contract is clear, you can completely replace the internals of a module and nothing breaks — as long as the new version honors the same contract. This is how large systems evolve over years. The contract is the stable surface; the implementation behind it can change freely.
You can test things in isolation
If you know the contract, you can test a module without needing the rest of the system. Send it the defined inputs, check that you get the defined outputs. If the contract is vague, you're guessing.
You can debug faster
"The output is wrong." Okay — does the input match the contract? If yes, the bug is inside the module. If no, the bug is in whoever is calling the module. Contract thinking lets you cut the search space in half immediately.
What Happens Without Contracts
Assumptions replace agreements
"I assumed it would handle that case." "I assumed the data would always be in that format." "I assumed it would return an error if something was wrong." Assumptions are bugs waiting to happen. Contracts replace assumptions with explicit agreements.
Changes break everything
Without a contract, changing a module's behavior is a gamble. You don't know what other modules depend on, because the dependency was never formally defined. You change one thing and discover four other modules were relying on behavior that was never promised — just coincidental.
Nobody knows what anything does
"What does this module accept?" → "Uh, look at the code." If the answer to "what's the contract?" is "read the implementation," there is no contract. And that means nobody really knows what it does without reverse-engineering it every time.
Integration is a nightmare
Two modules need to connect. Without contracts, integration day is chaos — mismatched data formats, unexpected nulls, inconsistent error handling. With contracts, integration is mechanical: both sides already agreed on the interface, so you plug them together and it works.
Interfaces vs. Implementations
This is a critical distinction that separates experienced engineers from everyone else:
The interface is what something does (the contract — inputs, outputs, errors).
The implementation is how it does it internally.
Other modules should only ever depend on the interface. They should never know or care about the implementation. This principle has a name — information hiding — and it is one of the most important ideas in engineering.
Why? Because implementations change constantly. Algorithms get optimized. Databases get swapped. Libraries get updated. But if every other module depends on the implementation details, every change is a catastrophe. If they only depend on the interface, changes are invisible to the outside world.
Think of it like a restaurant kitchen. You (the customer) have a contract with the restaurant: you order from the menu (input), you receive food that matches the description (output), and if they're out of something, they tell you (error). You don't know or care whether the chef uses a gas stove or electric, whether they prep ingredients at 6am or buy them pre-cut, whether there's one cook or five.
The menu is the interface. The kitchen is the implementation. As long as the food is right, you're happy.
Why This Is the Lesson That Separates Beginners From Professionals
Beginners think about how to make it work. Professionals think about how to define the interface so that anyone can make it work.
When an experienced engineer approaches a new problem, they don't start coding. They start defining contracts:
- "This module will accept a customer ID and return the customer's order history as a list of orders. If the customer doesn't exist, it returns an empty list, not an error."
- "This service will accept an image file up to 10MB in JPEG or PNG format and return a resized version. If the file is too large or the wrong format, it returns an error with a human-readable reason."
These statements are complete enough that anyone (or any LLM) could implement them. That's the point: the contract is the spec. If you can write clear contracts, you can build systems. If you can't, you're guessing — no matter how much code you know.