Contracts and Interfaces
An agreement between two parts of a system about what one side can send, what the other side will return, and what the rules are — so both sides can work independently without surprises.
What is it?
In everyday life, a contract is a written agreement: “I’ll deliver X, you’ll pay Y, and here’s what happens if something goes wrong.” In software, the idea is exactly the same. A contract defines how two parts of a system communicate — what goes in, what comes out, and what guarantees each side makes.
An interface is the specific surface where that communication happens. It’s the set of available operations, the expected data formats, and the rules for using them. The contract is the agreement; the interface is the doorway through which the agreement is carried out.
Contracts exist everywhere in software: between a frontend and a backend (the API contract), between code modules (type interfaces), between an application and its database (the schema), and between a component and its parent (props or parameters). Every time one piece of software hands data to another, there’s an implicit or explicit contract governing that handoff.
The difference between implicit and explicit contracts matters enormously. An implicit contract exists only in people’s heads — “I think the backend returns a list of objects with a name field.” An explicit contract is written down — in an API specification, a TypeScript type definition, or a database schema. Explicit contracts are verifiable, testable, and unambiguous. Implicit contracts are the source of most integration bugs.
In plain terms
A contract is like a menu in a restaurant. The menu tells you exactly what you can order, what each dish contains, and how much it costs. You don’t need to visit the kitchen to know what’s available. The chef doesn’t need to meet you to know what to prepare. The menu is the agreed interface between diner and kitchen.
At a glance
How contracts work between system parts (click to expand)
graph LR A[Caller] -->|sends request matching contract| C[Contract] C -->|forwards valid request| B[Provider] B -->|returns response matching contract| C C -->|forwards valid response| A style C fill:#4a9ede,color:#fffKey: The contract (blue) sits between the caller (the part requesting something) and the provider (the part fulfilling the request). Both sides build their code to match the contract. Neither needs to know how the other works internally — only that the contract is honoured.
How does it work?
A contract defines four things. Together, they form a complete agreement between two parts of a system.
1. Input format
What the caller must send. This includes:
- Required parameters — data that must always be present
- Optional parameters — data that can be included for more specific requests
- Data types — whether a value should be text, a number, a date, a list, etc.
- Validation rules — constraints like “must be a positive number” or “maximum 100 characters”
Example: "Send a GET request to /api/users with a required
'id' parameter (positive integer)"
Think of it like...
A form you fill in at a doctor’s office. Some fields are mandatory (name, date of birth), some are optional (preferred pharmacy). Each field expects a specific type of data — you can’t write your phone number in the date-of-birth field. The form is the input contract.
2. Output format
What the provider sends back. This includes:
- Response structure — the shape of the returned data
- Data types — what each field contains
- Guarantees — fields that will always be present in the response
{
"id": 42,
"name": "Ada Lovelace",
"email": "ada@example.com",
"created_at": "2026-01-15"
}The caller can build its entire user interface around this structure, confident that the response will always look like this.
3. Error cases
What happens when something goes wrong. A good contract doesn’t just define the happy path — it defines failures too:
- Not found — the requested item doesn’t exist
- Invalid input — the caller sent data in the wrong format
- Unauthorised — the caller doesn’t have permission
- Server error — something unexpected failed on the provider’s side
Think of it like...
A vending machine has implicit error contracts. Insert too little money? It rejects your selection and returns your coins. Press a button for an empty slot? It shows “sold out.” The machine communicates what went wrong in a predictable way.
Concept to explore
See apis for how error cases are communicated through HTTP status codes (404 for not found, 400 for bad request, 500 for server error).
4. Invariants
Guarantees that always hold, regardless of the specific request:
- “Every response will include a
total_countfield” - “Timestamps are always in UTC”
- “The API will never return more than 100 items per page”
- “Deleted items are never returned in list queries”
Invariants are the most powerful part of a contract because they let the caller make assumptions. If you know the API always paginates at 100 items, you can build your loading logic around that certainty.
Rule of thumb
The strongest contracts have explicit invariants. If both sides “just know” how something works without it being written down, that’s a bug waiting to happen.
Types of contracts in software
| Contract type | Where it lives | Between | Example |
|---|---|---|---|
| API contract | API spec / documentation | Frontend and backend | ”GET /users returns JSON array of user objects” |
| Database schema | Migration files / schema definition | Backend and database | ”Users table has columns: id (integer), name (text), email (text)“ |
| Type interface | Code (TypeScript, Java, etc.) | Code modules | interface User { id: number; name: string; } |
| Component props | Component definition | Parent and child UI components | ”UserCard expects a user object and an optional showEmail boolean” |
| Environment contract | Config files / documentation | Application and infrastructure | ”App expects DATABASE_URL and API_KEY environment variables” |
Concept to explore
See type-systems for how programming languages enforce contracts at the code level, catching violations before the software even runs.
Why do we use it?
Key reasons
1. Independence. When both sides agree on the contract, they can be built, changed, and deployed separately. The frontend developer doesn’t need to wait for the backend to be finished — they build against the contract.
2. Predictability. A contract eliminates guesswork. Instead of “I think the response looks like this,” you have “the contract says the response looks like this.” This matters especially when AI assistants generate code — they follow the contract, not a guess.
3. Testability. You can verify that each side honours its contract without testing the whole system. Does the backend return the right shape? Does the frontend handle the error cases? Each test is small and focused.
4. Safe evolution. When you need to change something, the contract tells you exactly what other parts depend on. You can add new fields safely (backwards compatible) or know exactly what breaks if you remove one (breaking change).
When do we use it?
- When two parts of a system communicate (frontend to backend, service to service, component to component)
- When different people or teams work on different sides of the same boundary
- When using AI coding assistants that need a clear specification to generate correct code
- When building a public API that external developers or services will consume
- When defining a database schema that multiple parts of the application depend on
Rule of thumb
If two parts of a system exchange data, write down the contract. The five minutes it takes to define the format will save hours of debugging mysterious mismatches later.
How can I think about it?
The electrical outlet
An electrical outlet is a contract and interface.
- The socket shape is the interface — it defines the physical connection point
- The voltage and frequency (230V / 50Hz in Europe) are the contract — both the outlet and the device must agree on these
- The plug on your device conforms to the contract — if it fits the socket and expects the right voltage, it works
- Any device that follows the contract works: lamps, toasters, laptops — the outlet doesn’t care what’s plugged in
- Violate the contract (wrong voltage) and something breaks
You can build a new appliance without consulting the power company, as long as you follow the electrical contract.
The postal system
The postal system runs on contracts.
- The envelope format is the interface — where to put the address, the return address, the stamp
- The postal rules are the contract — maximum size, required postage, no prohibited items
- Any sender can use the system if they follow the format — the post office doesn’t need to know what’s in the envelope
- Invariants exist: first-class mail arrives in 1-3 business days; tracked parcels generate a tracking number
- Error cases are defined: “return to sender” if the address is wrong, “postage due” if the stamp is insufficient
The sender and receiver never meet the sorting machines or mail carriers. They only interact with the postal contract (the envelope format and the rules).
Concepts to explore next
| Concept | What it covers | Status |
|---|---|---|
| apis | The most visible form of contracts in web development | complete |
| type-systems | How programming languages enforce contracts at compile time | stub |
| api-schemas | Formal specifications like OpenAPI that document API contracts | stub |
| technical-specification | The human-readable document that defines what to build | stub |
Some of these cards don't exist yet
They’ll be created as the knowledge system grows. A broken link is a placeholder for future learning, not an error.
Check your understanding
Test yourself (click to expand)
- Explain what a software contract is in your own words. Why is an explicit contract better than an implicit one?
- Name four things a well-defined contract specifies (the four parts described in “How does it work?”).
- Distinguish between a contract and an interface. How are they related but different?
- Interpret this scenario: a frontend developer updates their code to expect a
usernamefield, but the backend still returnsname. What went wrong in terms of contracts?- Connect contracts to separation-of-concerns. How do contracts make it possible for separated parts to work together without knowing each other’s internals?
Where this concept fits
Position in the knowledge graph
graph TD SD[Software Development] --> SA[Software Architecture] SA --> CI[Contracts and Interfaces] SA --> SoC[Separation of Concerns] SA --> ABS[Abstraction Layers] CI --> TS[Type Systems] CI --> AS[API Schemas] style CI fill:#4a9ede,color:#fffRelated concepts:
- apis — APIs are contracts in practice; every API call is governed by an agreed input/output format
- technical-specification — a higher-level contract between stakeholders about what the software should do
- abstraction-layers — contracts define the boundary between layers, specifying what each layer exposes to the one above
Further reading
Resources
- Contract-First API Development Explained (Finly Insights) — Why defining the contract before writing code leads to better systems
- API Design 101: From Basics to Best Practices (Javarevisited) — Beginner-friendly guide to designing clean API contracts
- Design by Contract (Software System Design) — The formal methodology of preconditions, postconditions, and invariants
- Explaining How an Interface Is a Contract (CS Educators Stack Exchange) — Multiple teaching analogies from computer science educators
- Building APIs with Contracts (kmcd.dev) — Practical guide to contract-based API development