Interface vs Type in TypeScript

Interfaces and type aliases both name object shapes in TypeScript, but they differ in merging, extension style, and what they can describe. Learn the key differences and when to choose each.

6 min read

TypeScript gives you two keywords, interface and type, to name an object shape. For most object types, they work the same way. The differences matter when you need declaration merging, when you want to describe something other than an object, or when you care about how errors appear.

The main distinction: an interface is open -- you can add properties to it later by declaring it again. A type alias is closed -- once declared, it cannot be changed.

Quick comparison

FeatureInterfaceType Alias
Describe object shapesYesYes
Describe unionsNoYes
Describe tuplesNoYes
Rename primitivesNoYes
Declaration mergingYesNo
Extend syntaxextends keyword& operator
Name in errorsAlwaysSometimes

Both can describe object shapes with the same property modifiers -- optional with a question mark and readonly with the readonly keyword. Both work interchangeably as parameter types, return types, and variable annotations.

Where they overlap: object shapes

For describing an object, the syntax is nearly identical. With an interface, you list properties inside curly braces using the interface keyword. With a type alias, you use the type keyword followed by an equals sign and the same object shape. Both produce the same result: the compiler checks that the object has the required properties with the correct types.

typescripttypescript
interface User {
  name: string;
  age: number;
}
 
const ada: User = { name: "Ada", age: 30 };

The same shape as a type alias uses the type keyword with an equals sign instead. Both approaches produce the same result: the compiler checks that the object has the required properties with the correct types. Here is the type alias version:

typescripttypescript
type User = { name: string; age: number };
 
const ada2: User = { name: "Ada", age: 30 };

Both approaches produce the same result. Both check that ada has name and age with the correct types. Both are erased at compile time. For this use case, the choice is purely about which keyword you prefer.

Where interfaces win: declaration merging

Interfaces can be declared multiple times in the same scope. TypeScript merges all declarations into a single interface with every property combined.

typescripttypescript
interface Window {
  title: string;
}
 
interface Window {
  version: number;
}
 
const win: Window = { title: "My App", version: 2 };

This is called declaration merging. It lets you add properties to an existing type without modifying the original source. Library authors use it to let consumers extend built-in types. Plugin systems use it to let plugins contribute their own fields to a shared config type.

Type aliases cannot do this. Declaring the same type alias twice produces a duplicate identifier error.

Where type aliases win: non-object types

Type aliases can name any TypeScript type. Interfaces can only describe object shapes. This means type aliases are the only choice for unions, tuples, and primitive renames.

typescripttypescript
type Status = "loading" | "success" | "error";
type Point3D = [number, number, number];
type Callback = (data: string) => void;

None of these can be expressed as an interface. An interface always describes a set of named properties on an object. A type alias can be anything.

How they extend differently

To build a new type from an existing one, interfaces use extends and type aliases use intersection.

typescripttypescript
interface Animal { name: string; }
interface Dog extends Animal { breed: string; }
 
type Animal = { name: string };
type Dog = Animal & { breed: string };

Both produce a type that requires name and breed. The difference is in how conflicts are handled. If two interfaces declare the same property with different types, the compiler reports an error. If two intersected type aliases have conflicting property types, the property resolves to never -- meaning no value can satisfy it -- but the alias itself is not flagged as an error until you try to use it.

typescripttypescript
type A = { value: string };
type B = { value: number };
type AB = A & B;
 
const x: AB = { value: "hello" };
// Error: Type 'string' is not assignable to type 'never'.

The error appears at the assignment, not at the type definition. With extends on interfaces, the error appears at the definition, which is usually easier to find and fix.

Error message clarity

Interfaces always appear by their original name in error messages. Type aliases may be expanded to their underlying type in some contexts, making errors harder to read.

Consider an error about a missing property. With an interface, you see "Type X is missing property Y from type User." With a type alias, you might see "Type X is missing property Y from type { name: string; age: number; }." The named form is easier to scan and search for.

When to use which

Use an interface when you are describing an object shape that may need to be extended later, when you want declaration merging, or when you prefer named types in error messages.

Use a type alias when the type is a union, intersection, tuple, or mapped type. Interfaces cannot describe these. Also use a type alias when the type is a one-off that will never be merged or extended.

For object shapes in your own code, the choice is mostly preference. Many teams follow a simple rule: default to interface for objects, and use type for everything else. This gives you declaration merging when you need it and the flexibility to name any type.

For deeper dives on each tool, see interfaces in TypeScript explained and type aliases in TypeScript explained.

Common mistakes

Trying to use extends on a type alias. Use an intersection with the ampersand operator instead.

Trying to declare the same type alias twice to add properties. Type aliases are closed. Use an interface if you need declaration merging.

Assuming the choice matters more than it does. For most object types, interfaces and type aliases are interchangeable. Do not spend too long deciding. Pick one, use it consistently, and move on to solving real problems.

Rune AI

Rune AI

Key Insights

  • Interfaces support declaration merging; type aliases do not.
  • Type aliases can name unions, tuples, and primitives; interfaces can only describe object shapes.
  • Extend interfaces with extends; combine type aliases with & (intersection).
  • Interfaces always appear by name in error messages; type aliases may not.
  • For object shapes the choice is mostly preference -- pick one style and be consistent.
RunePowered by Rune AI

Frequently Asked Questions

Can I use `extends` with a type alias?

No. Type aliases do not support the extends keyword. Use an intersection (`&`) instead to combine type aliases. For example: `type B = A & { extra: string }`.

Which is better for performance, interface or type?

Interfaces with extends can be more performant for the compiler than type aliases with intersections, especially in large codebases. For most projects, the difference is negligible.

Can type aliases appear in error messages?

Since TypeScript 4.2, type alias names may appear in error messages. However, interfaces always appear by their original name, which makes error messages more readable in many cases.

Conclusion

Interfaces and type aliases overlap heavily for object shapes. The key difference is that interfaces are open and support declaration merging, while type aliases are closed but can describe anything -- unions, intersections, tuples, and more. For object shapes, pick either and stay consistent. For anything else, reach for a type alias.