TypeScript

TypeScript Interfaces vs Type Aliases👨‍💻

Every TypeScript codebase eventually has this argument: interface or type? The internet is full of oversimplified advice ("just use type for everything"), but the real answer is more nuanced. Interfaces and type aliases have genuinely different capabilities, different error messages, and different roles in a well-structured codebase. Here is how they actually differ, and when each one earns its place.

Key Takeaways

  • 1Interfaces support declaration merging — multiple declarations with the same name are combined into one. Type aliases error on duplicate names. This makes interfaces essential for augmenting third-party libraries.
  • 2Interfaces use `extends` for inheritance, which produces clearer error messages when a property conflicts. Type aliases use `&` (intersection), which silently resolves conflicts to `never`.
  • 3Type aliases can represent unions, tuples, mapped types, conditional types, and primitive aliases. Interfaces cannot — they only describe object shapes.
  • 4Interfaces can be implemented by classes with `implements`, providing a compile-time contract. Type aliases work with `implements` too, but only when they describe an object shape.
  • 5The TypeScript compiler can cache interface types by name, which can improve type-checking performance in large codebases. Intersections are re-evaluated structurally each time.
  • 6In practice, use interfaces for object shapes you expect to extend or that form public API contracts. Use type aliases for everything else — unions, computed types, and type-level logic.

Master typescript interfaces vs type aliases

Take the TypeScript Essentials course with hands-on lessons and challenges.

Examples

extends vs & — the error message difference matters

typescript

When two interfaces declare conflicting properties, extends catches it immediately with a clear message pointing at the exact property. With type intersections, the conflicting property silently collapses to never, and you get a confusing error only when you try to assign a value — sometimes in a completely different file. This is the single strongest argument for preferring interface extends over type & for object hierarchies.

Declaration merging — extending Express, Prisma, and other libraries

typescript

Declaration merging is the one capability that type aliases will never have. Anytime you need to augment a type from a third-party library — Express Request, Window, ProcessEnv, Prisma Client — you must use an interface. This alone justifies keeping interfaces in your toolkit.

Type aliases shine — unions, discriminated unions, mapped types

typescript

Unions, discriminated unions, mapped types, and conditional types are the backbone of advanced TypeScript. None of them can be expressed as interfaces. If you banned type aliases, you would lose most of TypeScript's type-level expressiveness.

React component props — interfaces for extensible contracts

typescript

Component props are a textbook case for interfaces: they are object shapes, they form a hierarchy via extends, and downstream consumers may need to extend them. But the moment you need a union of those props or want to extract a single property type, you reach for a type alias. Both tools in the same file — that is normal.

implements — class contracts with interfaces

typescript

When a class declares implements Logger, TypeScript enforces that every method exists with the correct signature. If you rename a method or change a parameter type, you get an immediate compile error in the class — not a silent runtime failure. This is the original purpose of interfaces and it remains their strongest use case in OOP-heavy codebases.

Performance: interface caching vs intersection re-evaluation

typescript

The TypeScript compiler treats interfaces as named, cached types. When you hover over Order in your editor, you see the interface name. With the intersection version, the compiler must flatten the intersection structurally each time it checks compatibility. In small projects the difference is negligible. In monorepos with hundreds of models, the interface version compiles noticeably faster.

Common Mistakes

Mistake:

Using `type` for every object shape "because it's simpler" — then struggling when you need to augment a library type or getting confusing `never` errors from intersection conflicts

Fix:

Use interfaces for object shapes that form contracts or hierarchies. The extends keyword catches property conflicts at declaration time. Reserve type aliases for unions, tuples, mapped types, and type-level computation. Using both in the same file is not inconsistency — it is using the right tool for each job.

Mistake:

Attempting to use declaration merging with type aliases and getting confused by the "Duplicate identifier" error

Fix:

Only interfaces support declaration merging. If you need to augment a third-party type, you must use `declare module` with an interface declaration. If you have a type alias you want to extend, either convert it to an interface or use intersection: `type Extended = Original & { newProp: string }`.

Mistake:

Extending an interface with `&` instead of `extends` — mixing the two composition mechanisms within the same type hierarchy

Fix:

Pick one approach per inheritance chain. Interfaces use `extends`; type aliases use `&`. Mixing them works syntactically (`type Admin = User & { role: string }` where User is an interface), but it loses the clear error messages that `extends` provides on conflicts. If you started with interfaces, stay with `extends`.

Mistake:

Creating a union type with `interface` — trying to write something like `interface Status = 'active' | 'inactive'`

Fix:

Interfaces cannot represent unions, tuples, or primitive types. Use a type alias: `type Status = 'active' | 'inactive'`. If you catch yourself needing something that is not an object shape, you need a type alias — there is no workaround with interfaces.

Best Practices

  • Use interfaces for any object shape that serves as a contract — API response types, component props, service interfaces, repository patterns. They give you extends, declaration merging, and better error messages.
  • Use type aliases for unions, discriminated unions, tuples, mapped types, conditional types, and any type that is not a plain object shape. These are structurally impossible with interfaces.
  • Do not enforce a blanket "always use type" or "always use interface" rule across your codebase. The two constructs have different strengths. A file that uses both is not inconsistent — it is precise.
  • Prefer `extends` over `&` for building object type hierarchies. Interface extends produces actionable error messages on property conflicts; intersection silently creates `never` types that surface errors far from the source.
  • Use declaration merging deliberately for module augmentation (Express Request, Window globals, ProcessEnv). Document these augmentations with a comment explaining why they exist — declaration merging is powerful but can be surprising to developers who do not expect it.
  • When defining a public API (an npm package, a shared library), prefer interfaces for exported object types. Consumers can extend them via declaration merging if needed, which is impossible with exported type aliases.

Summary

Interfaces and type aliases are complementary, not competing. Interfaces are purpose-built for object shapes: they support extends with clear conflict errors, declaration merging for augmenting third-party types, and implements for class contracts. Type aliases handle everything else: unions, tuples, mapped types, conditional types, and primitive aliases. The practical rule is straightforward — interfaces for object contracts, type aliases for type-level computation. Most production codebases use both, and that is the correct approach.

Practice TypeScript with hands-on challenges

Learn typescript interfaces vs type aliases hands-on in your IDE

Interactive lessons and challenges on Stanza, practice in VS Code, Cursor, or the web.

Related Concepts

Related Cheatsheets

Master TypeScript with Stanza

Interactive lessons and challenges, right in your code editor.

Check the free courses. No credit card.