TypeScript gives you two ways to name a type: type aliases and interface declarations. For plain object shapes they are nearly interchangeable, which is exactly why the question "which should I use?" comes up on every team. This lesson lays out what each can do, the real differences, and a practical decision rule.
Both can describe an object shape, be extended, be implemented by classes, and be generic:
interface PointI { x: number; y: number }
type PointT = { x: number; y: number };
// Both extend:
interface Point3I extends PointI { z: number }
type Point3T = PointT & { z: number };
// Both work with classes:
class Vec implements PointI { x = 0; y = 0 }
class Vec2 implements PointT { x = 0; y = 0 }Because TypeScript is structural, PointI and PointT are fully assignable to each other. For a simple object shape, the choice is stylistic.
A type alias can name any type, not just object shapes:
type Id = string | number; // union
type Pair = [number, number]; // tuple
type Handler = (event: string) => void; // function
type Keys = keyof PointI; // operator result
type Maybe<T> = T | null | undefined; // generic union
type Getters<T> = { // mapped type
[K in keyof T as `get${string & K}`]: () => T[K];
};Unions, tuples, mapped types, conditional types, and template literal types can only be named with type. If your type is not an object shape, the decision is already made.
Declaration merging. Multiple interface declarations with the same name merge into one:
interface Window { myAppVersion: string }
// merges with the built-in Window type —
// window.myAppVersion is now typed everywhereThis is essential for augmenting global types and third-party libraries (declare module augmentation). A type alias with a duplicate name is simply an error.
Interfaces are also slightly friendlier in tooling: error messages and editor hovers show the interface name, while aliases are sometimes expanded into their full structure, which can be noisy for large types. In very large codebases interfaces can be marginally cheaper for the compiler when extended repeatedly, though for most projects the difference is unmeasurable.
interface extends and & usually produce the same result, but they handle conflicts differently. Extending with an incompatible property is an error at the declaration site — good, loud, early:
interface A { prop: string }
interface B extends A { prop: number } // Error: types of prop are incompatibleAn intersection silently produces prop: string & number, which collapses to never — the error surfaces later, somewhere confusing, when you try to create a value. This is a genuine argument for extends when building object hierarchies.
The official TypeScript documentation suggests using interface until you need features from type — a perfectly good default.
type can name unions, tuples, functions, and mapped/conditional types.interface supports declaration merging, which module augmentation depends on.extends surfaces property conflicts immediately; & can silently create never.Next lesson: tsconfig.json and Compiler Options — configuring TypeScript for real projects.