<arch.design/>
Principles/Interface / Contract
{ }CodeArchitecturebeginner1995oopcontractprotocolloose-coupling

Interface / Contract

Define what a component must do without dictating how it does it — so implementations can vary freely while callers remain stable.

5/5
{ }
Operates at: Code level

Inside a codebase — classes, modules, files

How it works

An interface (also called a contract or protocol) is a named set of method signatures. Any type that provides those methods satisfies the interface — it 'implements' the contract. The caller depends only on the interface, not on any concrete class.

This separation of 'what' from 'how' is the foundation of most software design principles. Dependency Injection works by injecting an interface. The Repository pattern exposes an interface. Hexagonal Architecture communicates through ports, which are interfaces. SOLID's Dependency Inversion Principle says high-level modules should depend on interfaces, not implementations.

Interfaces are a coordination primitive: a team can agree on an interface and implement it independently (database adapter, HTTP handler, ML model) — integration works as long as both sides honour the contract. Interfaces also make testing natural: swap the real implementation for a test double that implements the same interface.

Implementation

TypeScript · Go · Rust
interface Cache {
  get(key: string): string | null;
  set(key: string, value: string, ttlSeconds?: number): void;
  delete(key: string): void;
}

// Production: Redis
class RedisCache implements Cache {
  get(key: string)                        { /* Redis GET */ return null; }
  set(key: string, value: string, ttl?: number) { /* Redis SET EX */ }
  delete(key: string)                     { /* Redis DEL */ }
}

// Tests: in-memory — zero infrastructure, instant
class InMemoryCache implements Cache {
  private store = new Map<string, string>();
  get(key: string)  { return this.store.get(key) ?? null; }
  set(key: string, value: string) { this.store.set(key, value); }
  delete(key: string) { this.store.delete(key); }
}

// Service depends on the contract — never on a concrete class
class UserService {
  constructor(private cache: Cache) {}

  async getUser(id: string): Promise<User | null> {
    const hit = this.cache.get(`user:${id}`);
    if (hit) return JSON.parse(hit);
    const user = await db.findUser(id);
    if (user) this.cache.set(`user:${id}`, JSON.stringify(user), 300);
    return user;
  }
}

Why it matters

Code that depends on concrete classes is fragile — any change to that class ripples to all callers. Code that depends on an interface is stable — it keeps working as long as any conforming implementation exists, regardless of how many times the implementation changes internally.

When to use

  • Any time you want the ability to swap implementations (production vs test, v1 vs v2)
  • Defining seams between teams or modules
  • Third-party integrations (payment, email, storage) — hide behind an interface
  • Effectively always — if a component has collaborators, those should be interfaces

When NOT to use

  • Interfaces with a single implementation that will never vary add indirection for no benefit
  • Interfaces that leak implementation details (ISP violation) are harmful, not helpful

Trade-offs

+

Caller and implementation can evolve independently

Single-implementation interfaces add files and indirection with no benefit

+

Testing is natural — implement a test double in seconds

Interfaces must be designed carefully — wrong abstraction is costly to change

+

Multiple implementations can coexist and be selected at runtime

Interface discovery is harder in large codebases without good tooling

In production

Go standard library

io.Reader, io.Writer, http.Handler — tiny focused interfaces that compose the entire ecosystem

Java/Spring

Repository<T>, Service, Component — every Spring bean is accessed through an interface in well-designed apps

Rust traits

Display, Iterator, From, Into — the standard library is built on trait contracts; your types plug in by implementing them

Industry adoption

5/5Ubiquitous — used at virtually every scale-focused company.

Related principles