Difficulty: Beginner
Explain the difference between interfaces and type aliases in TypeScript. When would you use one over the other?
Both interfaces and type aliases define the shape of data, but they have different capabilities:
Interfaces: - Can be extended (extends keyword) - Support declaration merging (same name = merged) - Best for object shapes and class contracts
Type Aliases: - Can represent ANY type (unions, tuples, primitives, mapped types) - Cannot be merged (duplicate = error) - Best for unions, complex mapped types, utility types
Rule of thumb: Use interfaces for object shapes and public APIs, types for everything else.
// Interface - object shapes, extendable
interface User {
id: number;
name: string;
}
interface Admin extends User {
permissions: string[];
}
// Declaration merging (interfaces only)
interface User {
email: string; // merged into User
}
// User now has: id, name, email
// Type alias - any type, no merging
type ID = string | number; // union - interfaces can't do this
type Point = [number, number]; // tuple
type Handler = (e: Event) => void; // function
type Nullable<T> = T | null; // generic utility
Interfaces are best for defining object contracts. Type aliases are more flexible and can represent unions, tuples, and computed types.
// Discriminated union (tagged union)
type Shape =
| { kind: 'circle'; radius: number }
| { kind: 'rectangle'; width: number; height: number }
| { kind: 'triangle'; base: number; height: number };
function area(shape: Shape): number {
switch (shape.kind) {
case 'circle':
return Math.PI * shape.radius 2;
case 'rectangle':
return shape.width * shape.height;
case 'triangle':
return (shape.base * shape.height) / 2;
}
}
// TypeScript narrows the type in each case branch
// shape.radius is only accessible in 'circle' case
Discriminated unions use a common 'kind' field to narrow types. TypeScript's control flow analysis provides type safety in each branch.
type HasName = { name: string };
type HasAge = { age: number };
type HasEmail = { email: string };
// Intersection: must have ALL properties
type Person = HasName & HasAge & HasEmail;
const person: Person = {
name: 'Alice',
age: 30,
email: 'alice@example.com',
};
// Useful for mixins
function withTimestamp<T>(obj: T): T & { createdAt: Date } {
return { ...obj, createdAt: new Date() };
}
const user = withTimestamp({ name: 'Bob', role: 'admin' });
// type: { name: string; role: string; createdAt: Date }
Intersection types combine multiple types into one. The result must satisfy ALL constituent types.
Type Annotations, Interfaces, Type Aliases, Union Types, Intersection Types