Interfaces in TypeScript define the structure of objects, describing what properties and methods an object should have. They serve as contracts that enforce type safety across your codebase.
interface User { name: string; age: number;}// Can be extendedinterface Employee extends User { employeeId: string;}// Can be reopened (declaration merging)interface User { email?: string;}
type User = { name: string; age: number;}// Can use intersectiontype Employee = User & { employeeId: string;}// Cannot be reopened// type User = { ... } // Error!
When to use Interfaces:
Defining object shapes that might be extended
Working with classes
When you need declaration merging
Public APIs where consumers might want to extend types
When to use Types:
Union types: type Status = 'pending' | 'active' | 'inactive'
interface User { readonly id: string; name: string; age: number;}const user: User = { id: 'user-123', name: 'John', age: 30};// user.id = 'new-id'; // Error: Cannot assign to 'id' because it is a read-only property
One unique feature of interfaces is that multiple declarations with the same name automatically merge:
interface User { name: string; age: number;}interface User { email: string;}// Merged interface has all propertiesconst user: User = { name: 'John', age: 30, email: 'john@example.com'};
While declaration merging is powerful, use it carefully. It can make code harder to understand if overused. It’s most useful when augmenting third-party types.
From Exercise 4, interfaces can use literal types for discriminated unions:
interface User { type: 'user'; // Literal type name: string; age: number; occupation: string;}interface Admin { type: 'admin'; // Literal type name: string; age: number; role: string;}type Person = User | Admin;// TypeScript can discriminate based on the 'type' fieldfunction getInfo(person: Person) { if (person.type === 'user') { return person.occupation; // TypeScript knows this is User } else { return person.role; // TypeScript knows this is Admin }}