Structuring TypesIntermediate8 min04 / 7

Interfaces & Type Aliases

Learn how to describe the exact shape of your objects using TypeScript's interface and type alias — two of the most useful tools in the whole language.

Imagine you are the head chef at a restaurant. Every order that leaves the kitchen must have a plate, a main dish, and a table number. You could shout those rules at every cook every morning — or you could post a laminated card on the wall that everyone reads once.

In TypeScript, interfaces and type aliases are that laminated card. They describe the shape an object must have so TypeScript (and your teammates) always know what to expect. Write the shape once, and the compiler enforces it everywhere.

#Describing Object Shapes with interface

The User interface pins down exactly which fields every user object must carry.
interface User {
  id: number;
  name: string;
  email: string;
}

function greet(user: User): string {
  return `Hello, ${user.name}!`;
}

const alice: User = { id: 1, name: "Alice", email: "alice@example.com" };
console.log(greet(alice));

#Optional and Readonly Properties

Think of it like

readonly is like a printed receipt

Once a receipt is printed, you cannot change the amount. readonly works the same way — TypeScript lets you set the value once when you create the object, then refuses to let anything overwrite it later. Mark optional fields with ?, and TypeScript will accept the object with or without that field while reminding you to handle the missing case.

readonly blocks reassignment; ? marks a field as optional.
interface Product {
  readonly id: string;  // can never be changed after creation
  name: string;
  price: number;
  description?: string; // optional — may be undefined
}

const shirt: Product = { id: "p-42", name: "Linen Shirt", price: 49 };

shirt.name = "Cotton Shirt"; // OK
// shirt.id = "p-99";        // Error: read-only property

const label = shirt.description ?? "No description yet";
console.log(label);

#Extending Interfaces

Dog extends Animal, inheriting name and age and adding its own two fields.
interface Animal {
  name: string;
  age: number;
}

interface Dog extends Animal {
  breed: string;
  isGoodBoy: boolean;
}

const rex: Dog = {
  name: "Rex",
  age: 3,
  breed: "Labrador",
  isGoodBoy: true,
};

console.log(`${rex.name} is a ${rex.breed}.`);

#Type Aliases — The Other Way

TypeScript also has type, which lets you give any type expression a name — not just objects. You can name primitives, unions of string literals, tuples, or full object shapes. This flexibility is what makes type reach further than interface.

type can name anything. The & operator (intersection) merges two shapes so the result must satisfy both.
// A union of string literals
type Direction = "north" | "south" | "east" | "west";

// An object shape — works just like interface here
type Point = { x: number; y: number };

// Composing two shapes into one with &
type HasName = { name: string };
type HasAge  = { age: number };
type Person  = HasName & HasAge;

const dana: Person = { name: "Dana", age: 28 };
console.log(`${dana.name}, age ${dana.age}`);
Tip

interface vs type — a simple rule to start with

Default to `interface` for objects and classes — especially if you or others will extend them. Reach for `type` the moment you need a union (|), an intersection (&), a tuple, or a non-object alias like type ID = string. For plain object shapes either works; pick one and be consistent.

Common mistake

type aliases cannot be reopened

Interfaces support declaration merging — write interface Foo twice in the same scope and TypeScript silently combines them. Type aliases do not: declaring type Foo twice is an error. This matters most when writing library types that consumers need to augment. For everyday app code it rarely comes up, but now you know.

Quick check

Which TypeScript feature lets you mark a property so it can only be set when the object is first created and never changed afterward?

Key takeaways

  • interface describes an object's required shape; every field listed must be present.
  • Mark optional fields with ? and immutable fields with readonly.
  • Use extends to build a richer interface from a simpler base — the child inherits everything from the parent.
  • type aliases can name anything — unions, tuples, primitives, or object shapes — making them more flexible than interface.
  • Default to interface for objects; reach for type when you need a union (|), intersection (&), or any non-object concept.
Practice challenges
Test yourself · earn XP
0/4
Predict the output#1

The Product interface marks description as optional. What does this print?

predict-output
interface Product {
  readonly id: string;
  name: string;
  price: number;
  description?: string;
}

const shirt: Product = { id: "p-42", name: "Linen Shirt", price: 49 };
const label = shirt.description ?? "No description yet";
console.log(label);
Fix the bug#2

This code has a bug — what's wrong?

fix-bug
interface Product {
  readonly id: string;
  name: string;
  price: number;
}

const shirt: Product = { id: "p-42", name: "Linen Shirt", price: 49 };
shirt.id = "p-99";
Fill in the blank#3

Complete the interface so Dog inherits name and age from Animal while adding its own breed field.

interface Animal {
  name: string;
  age: number;
}

interface Dog  Animal {
  breed: string;
}

const rex: Dog = { name: "Rex", age: 3, breed: "Labrador" };
Reorder the lines#4

Arrange these lines to define two shapes and merge them into one Person type using intersection, then use it.

1
console.log(`${dana.name}, age ${dana.age}`);
2
type HasName = { name: string };
3
type Person = HasName & HasAge;
4
const dana: Person = { name: "Dana", age: 28 };
5
type HasAge = { age: number };
Your turn
Practice exercise

Define a BlogPost interface with: a readonly id (number), a title (string), an optional subtitle (string), a published (boolean), and tags (string array). Then write a function summarize(post: BlogPost): string that returns a string like "[42] Hello TypeScript — 3 tag(s)".

Try it live — edit the code and hit Run to see the output:

solution.ts · editable