DEV Community

Safal Bhandari
Safal Bhandari

Posted on

Type vs Interface in TypeScript

A clear, practical guide to the difference between type aliases and interface in TypeScript — when to use which, what you can (and can't) express with each, and the most common operations you’ll perform (including unions and intersections).


Quick summary

  • interface: primarily describes the shape of objects (and classes). Supports declaration merging, extends, and is favored for public object APIs and OOP-style code.
  • type: a more general alias that can name object shapes and primitives, unions, tuples, mapped/conditional types, function signatures, etc. It cannot be reopened (no declaration merging).

Both are largely interchangeable for plain object shapes, but each has unique strengths.


Step 7 - Types (what are types?)

type lets you aggregate data together similar to interface, but it can also do other things.

type User = {
  firstName: string;
  lastName: string;
  age: number;
}
Enter fullscreen mode Exit fullscreen mode

1. Unions

If you need a value that can be one of several types (e.g. a user id that can be a number or a string), type is the tool for that. Interfaces do not support union types directly.

type StringOrNumber = string | number;

function printId(id: StringOrNumber) {
  console.log(`ID: ${id}`);
}

printId(101); // ID: 101
printId("202"); // ID: 202
Enter fullscreen mode Exit fullscreen mode

2. Intersection

You can compose multiple object shapes into one by intersecting types. This creates a type that must satisfy all parts.

type Employee = {
  name: string;
  startDate: Date;
};

type Manager = {
  name: string;
  department: string;
};

type TeamLead = Employee & Manager;

const teamLead: TeamLead = {
  name: "harkirat",
  startDate: new Date(),
  department: "Software developer"
};
Enter fullscreen mode Exit fullscreen mode

Note: interface can achieve similar results with extends (see below), but the syntax and semantics differ slightly from intersections.


Core differences (detailed)

1. Declaration merging

  • interface supports declaration merging. If you declare the same interface name twice, TypeScript will merge the members.
interface Box { width: number }
interface Box { height: number }
// Box is now { width: number; height: number }
Enter fullscreen mode Exit fullscreen mode
  • type aliases cannot be reopened or merged. You’ll get an error if you try to redeclare the same type name.

2. Expressiveness

  • type can represent unions, intersections, primitives, tuples, mapped types, conditional types, and function signatures.
  • interface is mainly for object shapes (and callable/construct signatures), though it can represent function shapes and be extended.

3. extends vs & (extends vs intersection)

  • interface uses extends to inherit from other interfaces:
interface A { a: number }
interface B { b: string }
interface C extends A, B { c: boolean }
Enter fullscreen mode Exit fullscreen mode
  • type uses intersections to compose types:
type A = { a: number }
type B = { b: string }
type C = A & B & { c: boolean }
Enter fullscreen mode Exit fullscreen mode

Semantically they often produce the same resulting shape, but extends participates in declaration merging and some structural checks differently.

4. Implementing in classes

Both can be used for implements in classes, but interface is most idiomatic:

interface Flyable { fly(): void }
class Bird implements Flyable {
  fly() { console.log('flap') }
}
Enter fullscreen mode Exit fullscreen mode

You can also implements a type that describes a callable or object shape, but interface communicates intent more clearly for class contracts.

5. Readability and intent

  • interface signals: this is an object shape / API surface / contract to implement.
  • type signals: an alias — could be complex (tuple/union/primitive/etc.)

This is stylistic, but matters in team codebases.


Examples and edge-cases

Using type for primitives/unions/tuples

type ID = string | number;
type Point = [number, number];
Enter fullscreen mode Exit fullscreen mode

Using interface for objects and declaration merging

interface Config { url: string }
interface Config { timeout?: number }
// Config has both url and timeout now
Enter fullscreen mode Exit fullscreen mode

Function type: two ways

// type alias
type Fn = (x: number) => string;

// interface (call signature)
interface FnInterface { (x: number): string }
Enter fullscreen mode Exit fullscreen mode

Mapped & conditional types (only type)

type Keys = 'a' | 'b';
type Mapped = { [K in Keys]: number };

type Conditional<T> = T extends string ? string[] : number[];
Enter fullscreen mode Exit fullscreen mode

When to use which — practical guidelines

  1. Use interface when:
  • You’re describing public object shapes or library APIs.
  • You want to allow declaration merging (e.g., augmenting global interfaces or merging the same interface across modules).
  • You prefer extends syntax for composing object shapes and want a clear OOP-style contract.
  1. Use type when:
  • You need unions, tuples, primitives, mapped types, or conditional types.
  • You want to alias a complex expression (e.g., Partial<Record<string, any>> or string | number).
  • You want to build advanced generic utilities.
  1. When both work:
  • For simple object shapes, pick whichever your team prefers. Many style guides prefer interface for objects and type for everything else.

Interoperability

In practice, type and interface often interoperate fine. You can extends an interface from a type by first ensuring the type is an object type, or you can intersect types with interfaces by using & on the type side.


Short cheat-sheet

  • Need union/tuple/primitive/mapped/conditionaluse type.
  • Need declaration merging or an OOP-style contract → use interface.
  • Simple object shapes → either, prefer interface in many codebases.

Top comments (0)