Interface
“Program to an interface, not an implementation.” — Gang of Four, Design Patterns
Context
Whenever two parts of a system need to work together, they meet at a surface. An interface is that surface: the set of operations, inputs, outputs, and expectations through which one thing uses another. It operates at the architectural scale and is one of the most fundamental ideas in software construction.
Interfaces appear everywhere: a function signature is an interface, an HTTP API is an interface, a command-line tool’s flags are an interface, and the system prompt for an AI agent is a kind of interface. Wherever there is a boundary, there is an interface.
Problem
How do you let two parts of a system communicate without requiring each to know the other’s internals?
Forces
- Parts that know each other’s internals become tightly coupled. Changing one breaks the other.
- Making the interface too narrow limits what consumers can do.
- Making the interface too broad exposes details that should be hidden.
- Interfaces are hard to change once consumers depend on them.
Solution
Define the interface as the minimum surface a consumer needs to accomplish its goals. An interface should answer: what can I ask for, what do I provide, and what can I expect in return? Everything else (the data structures, algorithms, and strategies behind the interface) belongs to the implementation.
Good interfaces are:
- Discoverable — a consumer can figure out what is available.
- Consistent — similar operations work in similar ways.
- Stable — they change rarely, and when they do, changes are backward-compatible where possible.
- Documented — the contract is explicit, not guessed at.
In agentic coding, interfaces take on special importance. An AI agent’s ability to use a tool depends entirely on the quality of the tool’s interface description. A well-documented function with clear parameter names and return types is easy for an agent to call correctly. A function with ambiguous parameters and side effects is a trap.
How It Plays Out
A team defines a StorageService interface with methods like save(key, data) and load(key). One implementation writes to a local filesystem, another to cloud storage. The rest of the application uses the interface without caring which implementation is behind it. When performance requirements change, they swap implementations without touching the callers.
An AI agent is given access to a set of tools: read_file, write_file, run_tests. Each tool has a clear interface: name, description, parameters, and return value. The agent can plan its work by reasoning about what each tool does, without knowing how they’re implemented. If the tool descriptions are vague (“does stuff with files”), the agent will misuse them.
“Define a StorageService interface with save(key, data) and load(key) methods. Write two implementations: one for local filesystem and one for S3. The rest of the app should use only the interface.”
Consequences
Well-designed interfaces enable abstraction, support independent development, and make testing easier (you can substitute a mock implementation). They are the foundation of pluggable, extensible systems.
The cost is rigidity: once an interface is published and consumers depend on it, changing it requires careful coordination. This is why interface design deserves more thought than implementation design. The implementation can always be rewritten, but the interface is a promise.
Related Patterns
- Enables: Abstraction — an interface is the visible face of an abstraction.
- Defines: Contract — the contract spells out what the interface promises.
- Used by: Component, Module — every component and module exposes an interface.
- Consumed by: Consumer — someone or something uses every interface.
- Lives at: Boundary — interfaces exist where boundaries exist.