Interfaces vs Type Aliases

TypeScript · Lesson 8 of 28 6 min read Updated

In the last lesson you described objects with type. TypeScript has a second way to name an object shape: the interface keyword. For simple objects the two are almost the same, which confuses many beginners. Which one should you use, and when does the difference matter?

In this lesson you will write interfaces, extend them, combine types with intersections, see the few real differences between interfaces and type aliases, and learn a simple rule for choosing.

Your first interface

An interface looks like a type alias without the = sign:

TypeScriptRun it yourself
interface Teacher {
  id: number;
  name: string;
  subjects: string[];
  phone?: string;
  greet(): string;
}

const meera: Teacher = {
  id: 7,
  name: "Meera Iyer",
  subjects: ["Maths", "Physics"],
  greet() {
    return `Good morning, I am ${this.name}`;
  },
};

console.log(meera.greet());
console.log(`${meera.name} teaches ${meera.subjects.join(" and ")}`);
console.log(meera.phone ?? "phone not shared");
Output
Good morning, I am Meera Iyer
Meera Iyer teaches Maths and Physics
phone not shared

Everything you learned about object types works the same: optional properties with ?, readonly, nested types, index signatures and structural typing. greet(): string is method syntax: it describes a function property. You could also write it as greet: () => string.

The same shape written as a type alias would be:

TypeScript
type Teacher = {
  id: number;
  name: string;
  subjects: string[];
  phone?: string;
  greet(): string;
};

Extending interfaces and intersecting types

Real data often shares common fields. An interface can extend one or more other interfaces, copying in all their properties. Type aliases do the same job with an intersection, written with &, which means "has everything from both types".

TypeScriptRun it yourself
interface Person {
  name: string;
  city: string;
}

interface Contact {
  phone: string;
}

// interface style: extends
interface Employee extends Person, Contact {
  employeeId: number;
  salary: number;
}

// type alias style: intersection
type Customer = Person & Contact & {
  loyaltyPoints: number;
};

const kabir: Employee = {
  name: "Kabir", city: "Jaipur", phone: "98290 00000", employeeId: 301, salary: 42000,
};
const priya: Customer = {
  name: "Priya", city: "Pune", phone: "98220 11111", loyaltyPoints: 1250,
};

function sendSms(to: Contact & Person, text: string): void {
  console.log(`SMS to ${to.name} (${to.phone}): ${text}`);
}

sendSms(kabir, "Salary credited");
sendSms(priya, `You have ${priya.loyaltyPoints} points`);
Output
SMS to Kabir (98290 00000): Salary credited
SMS to Priya (98220 11111): You have 1250 points

Both styles produce a type with all the properties. Notice the parameter type Contact & Person: you can also write an intersection inline, without naming it.

There is one practical difference. When two parts disagree about a property, extends reports an error right away, while an intersection quietly produces an impossible type:

TypeScript
interface Item {
  price: number;
}

interface SaleItem extends Item {
  price: string;
}

type Mixed = Item & { price: string };
const x: Mixed = { price: 100 };
Output
app.ts(5,11): error TS2430: Interface 'SaleItem' incorrectly extends interface 'Item'.
  Types of property 'price' are incompatible.
    Type 'string' is not assignable to type 'number'.
app.ts(10,20): error TS2322: Type 'number' is not assignable to type 'never'.

The interface error points at the real mistake. The intersection makes price both a number and a string at once, which is impossible, so its type becomes never (a type with no values) and the error only shows up later where you use it. The TypeScript team also notes that extends is checked faster than large intersections in big projects.

Declaration merging

Interfaces have one feature type aliases lack: if you declare the same interface name twice, TypeScript merges the declarations into one. A type alias name can only be declared once.

TypeScriptRun it yourself
interface AppSettings {
  language: string;
}

interface AppSettings {
  darkMode: boolean;
}

const settings: AppSettings = { language: "hi", darkMode: true };
console.log(settings);
Output
{ language: 'hi', darkMode: true }

You would rarely merge your own interfaces on purpose. Merging is useful for adding properties to types defined by someone else, such as a library or the browser's Window. You will see this in Declaration Files & DefinitelyTyped. The flip side is that an accidental second interface with the same name silently merges, while a duplicate type alias gives error TS2300: Duplicate identifier.

What only type aliases can do

An interface always describes an object shape. A type alias can name any type:

TypeScript
type Pincode = string;                           // a primitive
type PaymentMode = "upi" | "card" | "cash";      // a union of literals
type Point = [x: number, y: number];             // a tuple
type Formatter = (amount: number) => string;     // a function type
type Nullable<T> = T | null;                     // a generic helper

Unions, tuples, function types and the advanced types you will meet later (mapped and conditional types) all need type.

Which should you use?

Featureinterfacetype
Describe an object shapeYesYes
Extend or combineextends& intersection
Unions, tuples, primitives, functionsNoYes
Declaration mergingYesNo (duplicate name error)
Class can implements itYesYes (if it is an object type)

A simple, widely used rule:

  • Use interface for object shapes, especially ones that other types extend or that a library exposes.
  • Use type for everything else: unions, tuples, function types and type transformations.
  • Above all, be consistent within a project. Both work, and mixing them randomly confuses readers.

Common mistakes

  • Writing interface Mode = "upi" | "card". Interfaces cannot describe unions; use type.
  • Expecting an intersection to warn about conflicts. A & B with clashing properties gives never; prefer extends for object inheritance.
  • Accidental merging. Two files that both declare a global interface Config merge into one type. Use modules so names do not collide.
  • Endless debates. For a plain object, interface or type alias makes no difference. Pick one rule and follow it.

Key points

  • interface Name { ... } describes an object shape, just like type Name = { ... }.
  • Interfaces extend with extends; type aliases combine with the & intersection.
  • Conflicting properties are an error with extends but silently become never with &.
  • Interfaces with the same name merge; type alias names must be unique.
  • Only type aliases can name unions, tuples, primitives and function types.

Exercise

Create an interface Vehicle with regNumber and wheels. Extend it into Bus (adds seats and route) and Truck (adds maxLoadKg). Then write a type alias Fleet for an array that may hold buses or trucks, and a function countSeats(fleet: Fleet): number that adds up the seats of all buses. Hint: check "seats" in v.

Show solution
TypeScript
interface Vehicle {
  regNumber: string;
  wheels: number;
}

interface Bus extends Vehicle {
  seats: number;
  route: string;
}

interface Truck extends Vehicle {
  maxLoadKg: number;
}

type Fleet = (Bus | Truck)[];

function countSeats(fleet: Fleet): number {
  let total = 0;
  for (const v of fleet) {
    if ("seats" in v) total += v.seats;
  }
  return total;
}

const depot: Fleet = [
  { regNumber: "HR12 AB 1234", wheels: 6, seats: 52, route: "Rohtak-Delhi" },
  { regNumber: "HR12 CD 5678", wheels: 10, maxLoadKg: 16000 },
  { regNumber: "HR12 EF 9012", wheels: 6, seats: 40, route: "Rohtak-Hisar" },
];
console.log(countSeats(depot)); // 92

The interfaces describe object shapes, and the type alias Fleet is needed for the union. The in check tells TypeScript that v is a Bus inside the if.

Quick Quiz

1. Which can describe a union such as "upi" | "card"?

2. What happens when you declare interface Settings twice in the same scope?

3. What is the type alias equivalent of interface C extends A, B {}?

4. What does an intersection of { price: number } and { price: string } give for price?

Learn TypeScript with a mentor

Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call