Defining Interfaces
An interface describes the shape of an object. It's one of the most common ways to define types in TypeScript:
interface User {
name: string;
email: string;
age: number;
}
const user: User = {
name: "Sabaoon",
email: "sabaoon@example.com",
age: 25,
};If you miss a required property or add an extra one, TypeScript will catch it:
// Error: Property 'email' is missing
const bad: User = { name: "Alice", age: 30 };Optional & Readonly Properties
interface BlogPost {
title: string;
content: string;
tags?: string[]; // optional
readonly publishedAt: Date; // cannot be changed after creation
}
const post: BlogPost = {
title: "Hello TypeScript",
content: "...",
publishedAt: new Date(),
};
// post.publishedAt = new Date(); // Error: read-only
post.tags = ["typescript"]; // OK — optional properties can be set later
Extending Interfaces
Interfaces can extend one or more other interfaces to build up complex types incrementally:
interface Timestamped {
createdAt: Date;
updatedAt: Date;
}
interface SoftDeletable {
deletedAt: Date | null;
}
interface User extends Timestamped, SoftDeletable {
id: string;
name: string;
email: string;
}
// User now requires all properties from all three interfaces
const user: User = {
id: "1",
name: "Sabaoon",
email: "sabaoon@example.com",
createdAt: new Date(),
updatedAt: new Date(),
deletedAt: null,
};Type Aliases
Type aliases create a name for any type, not just objects:
// Alias for a union
type Status = "active" | "inactive" | "pending";
// Alias for a function signature
type Formatter = (input: string) => string;
// Alias for an object (looks similar to an interface)
type Point = {
x: number;
y: number;
};
// Alias for a tuple
type RGB = [red: number, green: number, blue: number];Interface vs Type Alias
Both can describe object shapes, but they have key differences:
// Interface — can be extended and merged
interface Animal {
name: string;
}
interface Animal {
sound: string; // declaration merging: both declarations combine
}
const dog: Animal = { name: "Rex", sound: "Woof" }; // requires both
// Type — cannot be merged, but can represent unions, tuples, primitives
type Pet = Animal & { owner: string }; // intersection instead of extends
When to use which:
| Feature | interface | type |
|---|---|---|
| Object shapes | Yes | Yes |
| Extend / inherit | extends keyword | & intersection |
| Declaration merging | Yes | No |
| Unions | No | Yes |
| Tuples | No | Yes |
| Primitives | No | Yes |
Rule of thumb: Use interface for object shapes (especially in libraries), use type for everything else (unions, tuples, computed types).
Index Signatures
When you don't know all property names in advance:
interface Dictionary {
[key: string]: string;
}
const colors: Dictionary = {
primary: "#E21B1B",
secondary: "#1a1a1a",
background: "#ffffff",
};
// You can combine known and dynamic properties
interface Config {
env: "development" | "production";
[key: string]: string;
}Record Utility Type
Record is a cleaner alternative to index signatures for simple key-value maps:
// Record<KeyType, ValueType>
type UserRoles = Record<string, "admin" | "editor" | "viewer">;
const roles: UserRoles = {
alice: "admin",
bob: "editor",
charlie: "viewer",
};
// With a union key for strict keys
type Theme = Record<"light" | "dark", { bg: string; text: string }>;
const theme: Theme = {
light: { bg: "#fff", text: "#000" },
dark: { bg: "#000", text: "#fff" },
};
Intersection Types
Combine multiple types into one using &:
type HasId = { id: string };
type HasTimestamps = { createdAt: Date; updatedAt: Date };
type Entity = HasId & HasTimestamps;
// Entity requires id, createdAt, and updatedAt
const entity: Entity = {
id: "abc",
createdAt: new Date(),
updatedAt: new Date(),
};This is the type equivalent of interface extends — both let you compose smaller types into larger ones.