Overview
interface and type overlap on object shapes and differ in four places: declaration merging, how extension conflicts surface, which constructs each can express, and how errors display. This page sets one team-wide default so the choice stops being a review debate. Umbrella rules are in typescript.
Default to type
type covers unions, intersections, tuples, primitives, mapped types, and conditional types; interface covers none of those. One keyword for everything keeps a codebase consistent. This is a house default, not a language rule: TypeScript’s own handbook suggests interface for object types until you need a type feature, and both work with implements.
type Status = "draft" | "sent" | "paid"
type Entries<T> = { [K in keyof T]: [K, T[K]] }[keyof T]Use interface for public shapes that consumers augment
Declarations of the same interface name merge; a second type alias with the same name is a duplicate-identifier error. That is why ambient and library types (Window, Express Request, Vite’s ImportMeta) are interfaces, and why a library that wants users to extend its context type should export an interface.
export interface RequestContext { requestId: string }
declare module "@acme/http" {
interface RequestContext { traceId: string }
}In application code accidental merging is a bug, not a feature.
Extend with extends when the relationship is “B is an A plus more”
interface B extends A reports a conflicting field at the declaration. type B = A & {...} instead intersects the field types, and a conflict collapses to never without an error.
interface A { x: string }
// interface B extends A { x: number } // Error: incompatible
type B2 = A & { x: number } // compiles; B2["x"] is neverUse & when you deliberately combine independent shapes.
Measure before switching for compile speed
TypeScript’s performance notes favor interface extends over deep intersection chains, because an interface is resolved to one flat object type and cached, while intersections are recomputed. The difference matters in library code with hundreds of derived shapes and is invisible in most applications. Profile a slow build first (typescript-tsconfig), and treat the change as one lever among several; the TypeScript 7 native compiler may shift where the time goes.
Use type for props
Component props are not augmented by consumers, and the union, intersection, and utility-type ergonomics pay off. Use interface only for a published component library whose users augment props through module augmentation. See react.
type ButtonProps = {
variant: "primary" | "secondary"
} & Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "children">