Skip to content

The interface is the decision

Most of what a module will cost you is settled by its interface, before a line of its body is written.

· updated

You can rewrite a module's body on a quiet afternoon. You cannot rewrite its interface without touching every caller, and the callers are the part you do not control. So the interface is where the decision actually gets made, and it gets made early, usually before anyone has seen the problem clearly enough to make it well.

What follows is what I do about that.

The first move is to ask what a deep module would look like here — one that hides enough behaviour that the caller can forget the rest.

Three questions, in order

  1. What does the caller already know?
  2. What would it have to learn to use this?
  3. Is the difference worth the module existing?

The third question is the one that kills modules, and it should. A wrapper that renames its dependency has a cost and no depth — and a type alias like type Id = string is the same trade in miniature.

The signature carries the whole argument:

src/graph/build.ts
interface ContentGraph {
  nodeAt(route: string): Node | null
  incomingReferences(termSlug: string): Node[]
  routes(): Route[]
}

Three methods, and everything downstream is a pure function of them. The slug derivation, the canonical construction, the pagination, the consistency assertions — none of that appears here, which is the point.

A narrow interface is a promise you can keep.

What this rules out

  • Configuration objects that grow a field per caller
  • Optional parameters that change the return type
  • Anything named helpers
A wide interface over a thin body, beside a narrow interface over a thick one
The two shapes, drawn to the same scale. Depth is the ratio, not the size.

The vocabulary here is John Ousterhout's, and the book is short enough to read in a sitting.

Interface depth explorer
---
title: "The interface is the decision"
description: "Most of what a module will cost you is settled by its interface, before a line of its body is written."
url: "https://lateano.com/writing/the-interface-is-the-decision"
author: "Colin Lateano"
published: "2026-07-11"
updated: "2026-07-19"
tags:
  - title: "How I work"
    url: "https://lateano.com/tags/how-i-work"
---

# The interface is the decision

Most of what a module will cost you is settled by its interface, before a line of its body is written.

You can rewrite a module's body on a quiet afternoon. You cannot rewrite its interface without touching every caller, and the callers are the part you do not control. So the interface is where the decision actually gets made, and it gets made early, usually before anyone has seen the problem clearly enough to make it well.

## What follows is what I do about that.

The first move is to ask what a [deep module](/glossary/deep-module) would look like here — one that hides enough behaviour that the caller can forget the rest.

### Three questions, in order

1. What does the caller already know?
2. What would it have to learn to use this?
3. Is the difference worth the module existing?

The third question is the one that kills modules, and it should. A wrapper that _renames_ its dependency has **a cost and no depth** — and a type alias like `type Id = string` is the same trade in miniature.

> [!WARNING]
> A wide interface is not a style problem. It is a commitment to every caller that reaches through it, and you find out how many there were on the day you try to change it.

The signature carries the whole argument:

```ts title="src/graph/build.ts"
interface ContentGraph {
  nodeAt(route: string): Node | null
  incomingReferences(termSlug: string): Node[]
  routes(): Route[]
}
```

Three methods, and everything downstream is a pure function of them. The slug derivation, the canonical construction, the pagination, the consistency assertions — none of that appears here, which is the point.

> A narrow interface is a promise you can keep.

## What this rules out

- Configuration objects that grow a field per caller
- Optional parameters that change the return type
- Anything named `helpers`

![A wide interface over a thin body, beside a narrow interface over a thick one](https://cdn.sanity.io/images/145b6x1x/production/a318be46401d8e4c739261375899f106a8e9b736-1200x800.png?w=1400&fit=max&auto=format "The two shapes, drawn to the same scale. Depth is the ratio, not the size.")

The vocabulary here is [John Ousterhout's](https://web.stanford.edu/~ouster/cgi-bin/aposd.php), and the book is short enough to read in a sitting.

[Interface depth explorer](https://example.com/fixtures/interface-depth-explorer)

> [!ASIDE]
> I have changed my mind about this once already. The earlier version of this argument said the body mattered more, and it was wrong for a reason worth its own post.
>
> What changed my mind was wanting a [seam](/glossary/seam) I had not left myself, which is a body problem you can only reach through the interface.