Defining Interfaces
Defining Interfaces
Interfaces describe the shape of objects. They define what properties and methods an object must have.
Basic Interface
interface User {
id: number;
name: string;
email: string;
}
const user: User = {
id: 1,
name: 'Alice',
email: 'alice@example.com'
};
// Error: missing property 'email'
const badUser: User = {
id: 2,
name: 'Bob'
};
Optional Properties
interface UserProfile {
id: number;
name: string;
bio?: string; // optional
avatar?: string; // optional
}
const profile: UserProfile = {
id: 1,
name: 'Alice'
// bio and avatar are optional
};
Readonly Properties
interface Config {
readonly apiUrl: string;
readonly timeout: number;
}
const config: Config = {
apiUrl: 'https://api.example.com',
timeout: 5000
};
// config.apiUrl = 'other'; // Error: cannot assign to readonly
Function Types in Interfaces
interface MathOperations {
add(a: number, b: number): number;
subtract: (a: number, b: number) => number;
}
const math: MathOperations = {
add: (a, b) => a + b,
subtract: (a, b) => a - b
};
Extending Interfaces
Extending Interfaces
Interfaces can extend other interfaces to build complex type hierarchies.
Single Inheritance
interface Animal {
name: string;
age: number;
}
interface Dog extends Animal {
breed: string;
bark(): void;
}
const myDog: Dog = {
name: 'Rex',
age: 5,
breed: 'Labrador',
bark() { console.log('Woof!'); }
};
Multiple Inheritance
interface Printable {
print(): void;
}
interface Loggable {
log(): void;
}
interface Document extends Printable, Loggable {
title: string;
content: string;
}
const doc: Document = {
title: 'My Doc',
content: 'Hello World',
print() { console.log(this.title); },
log() { console.log(this.content); }
};
Extending Multiple Interfaces
interface Timestamped {
createdAt: Date;
updatedAt: Date;
}
interface SoftDeletable {
deletedAt: Date | null;
}
interface BaseEntity {
id: number;
}
interface User extends BaseEntity, Timestamped, SoftDeletable {
name: string;
email: string;
}
Interface Merging (Declaration Merging)
interface Window {
myCustomProp: string;
}
// This merges with the existing Window interface
// allowing myCustomProp to be used on window object
Structural Typing
Structural Typing
TypeScript uses structural typing: objects are compatible if they have the same shape, regardless of explicit declaration.
Structural Compatibility
interface Point {
x: number;
y: number;
}
function logPoint(p: Point) {
console.log(`${p.x}, ${p.y}`);
}
// This works - the object has the right shape
class MyPoint {
x: number;
y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
}
const mp = new MyPoint(10, 20);
logPoint(mp); // OK - MyPoint is structurally compatible with Point
Excess Property Checks
interface Square {
width: number;
height: number;
}
// Error: object literal may only specify known properties
const sq: Square = {
width: 10,
height: 10,
color: 'red' // Error!
};
// Workaround: assign to intermediate variable
const tmp = { width: 10, height: 10, color: 'red' };
const sq2: Square = tmp; // OK
Index Signatures
interface StringMap {
[key: string]: string;
}
const dict: StringMap = {
hello: 'world',
foo: 'bar'
};
interface NumberDict {
[key: string]: number;
length: number; // must match index signature type
}
Record Utility Type
// Record is equivalent to an interface with index signature
const scores: Record<string, number> = {
alice: 95,
bob: 87
};
Type vs Interface
Type vs Interface
Both type aliases and interfaces define object shapes, but have key differences.
When to Use Interface
// Interfaces support declaration merging
interface User {
name: string;
}
// This extends the same interface
interface User {
age: number;
}
// Result: User has both name and age
const user: User = { name: 'Alice', age: 30 };
// Interfaces are better for: object shapes, class contracts, extendable APIs
interface Repository<T> {
findById(id: string): Promise<T>;
findAll(): Promise<T[]>;
save(entity: T): Promise<T>;
delete(id: string): Promise<void>;
}
When to Use Type Alias
// Types support unions and intersections
type Result<T> = Success<T> | Failure;
// Types can compute new types
type UserKeys = keyof User;
type UserName = User['name'];
// Types can use mapped types
type Readonly<T> = { readonly [K in keyof T]: T[K] };
// Types can be primitives
type ID = string;
type Callback = () => void;
Comparison Table
| Feature | Interface | Type Alias |
|---|---|---|
| Declaration Merging | Yes | No |
| Union Types | No | Yes |
| Intersection | Yes (extends) | Yes (&) |
| Implements (class) | Yes | Yes |
| Map/Filter types | No | Yes |
| Extends other types | Yes | Yes |
Best Practice
- Use interfaces for object shapes and class contracts
- Use type aliases for unions, intersections, and computed types
- Be consistent within your codebase