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
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
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.
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
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.
// 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}`);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.
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.
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.
The Product interface marks description as optional. What does this print?
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);This code has a bug — what's wrong?
interface Product {
readonly id: string;
name: string;
price: number;
}
const shirt: Product = { id: "p-42", name: "Linen Shirt", price: 49 };
shirt.id = "p-99";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" };
Arrange these lines to define two shapes and merge them into one Person type using intersection, then use it.
console.log(`${dana.name}, age ${dana.age}`);type HasName = { name: string };type Person = HasName & HasAge;
const dana: Person = { name: "Dana", age: 28 };type HasAge = { age: number };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: