"A type alias is just a name for a shape. The shape is the real thing."
What type actually does
A type declaration introduces a new name for an existing type. type UserId = number creates a name UserId that resolves to number wherever it's used. It is not a new type — UserId and number are interchangeable everywhere. The alias is for the reader, not the compiler.
But aliases are not weak. They get powerful when the right-hand side stops being a primitive. type Status = 'idle' | 'loading' | 'done' names a literal union. type Callback = (msg: string) => void names a function signature. type Tree<T> = { value: T; children: Tree<T>[] } names a recursive shape. None of these have an interface equivalent without compromises.
The three places type is the right tool
1. Unions and intersections. type StringOrNumber = string | number, type Combined = A & B. Interfaces can't express unions at all and only barely express intersections (via extends).
2. Derived types from existing types. type UserKeys = keyof User, type UserEmail = User['email'], type Awaited2 = Awaited<Promise<string>>. These computations need type — interface can't host them.
3. Function types in isolation. type Handler = (event: Event) => void reads cleaner than the equivalent interface (interface Handler { (event: Event): void }, which uses an obscure call-signature syntax).
Where type and interface overlap
For plain object shapes — what most developers reach for most of the time — type and interface are nearly equivalent:
type UserA = { id: number; name: string };
interface UserB { id: number; name: string }
Both compile to the same checks. Both can be extends-ed (interface uses extends User, type uses intersection & User). Both can be exported. The difference is style, not capability — within this overlap.
type whenever the type isn't a plain object shape. Unions, intersections, function types, derived types, conditional types, mapped types — all of these want type. Plain object shapes are the only overlap region, and there it's a style call.Recursive types in aliases
One of the most useful things about type is that it can refer to itself. type Tree<T> = { value: T; children: Tree<T>[] } defines a recursive tree shape. The recursive reference is fine because TypeScript resolves it lazily. Interfaces can do this too, but the shape declarations of recursive types often read more cleanly as type.
Pippa's confession
types.ts file where most exports are type rather than interface. The reason is mechanical: a lot of the types are unions (type BrainName = 'claude' | 'codex' | 'gemini' | 'ollama') or derived from API responses (type ChatMessage = Awaited<ReturnType<typeof fetchMessage>>). For pure object shapes the codebase still occasionally uses interface, but type is the default because most of what's expressed isn't pure object shape.