Objects are central to JavaScript and TypeScript. Interfaces and type aliases let you define the shape of objects, ensuring consistency and catching errors before they happen.
You can define object types inline or extract them into reusable interfaces:
// Inline object type
function printPlayer(player: { name: string; level: number }) {
console.log(`${player.name} is level ${player.level}`);
}
printPlayer({ name: "Hero", level: 5 });
// Object type with optional properties
function createUser(config: {
username: string;
email?: string; // Optional
age?: number;
}) {
return config;
}
createUser({ username: "alice" });
createUser({ username: "bob", email: "bob@example.com", age: 30 });
Interfaces are the recommended way to define object shapes in TypeScript. They're reusable, extendable, and self-documenting:
// Define an interface
interface Player {
name: string;
health: number;
level: number;
isAlive: boolean;
}
// Use the interface
const hero: Player = {
name: "Aragon",
health: 100,
level: 5,
isAlive: true
};
// Function using interface
function healPlayer(player: Player, amount: number): void {
player.health += amount;
console.log(`${player.name} healed for ${amount} HP`);
}
healPlayer(hero, 25);
Interfaces support optional properties (?) and readonly
properties:
interface GameConfig {
readonly maxPlayers: number; // Cannot be changed after creation
difficulty: string;
soundEnabled?: boolean; // Optional property
musicVolume?: number; // Optional property
}
const config: GameConfig = {
maxPlayers: 4,
difficulty: "normal"
};
// config.maxPlayers = 8; // ✗ Error: Cannot assign to 'maxPlayers'
config.difficulty = "hard"; // ✓ OK
// Optional properties can be undefined
if (config.soundEnabled === undefined) {
config.soundEnabled = true;
}
Interfaces can extend other interfaces, creating hierarchies:
// Base interface
interface Character {
name: string;
health: number;
}
// Extended interface inherits base properties
interface Warrior extends Character {
weaponType: string;
armor: number;
}
interface Mage extends Character {
mana: number;
spells: string[];
}
const knight: Warrior = {
name: "Sir Galahad",
health: 150,
weaponType: "sword",
armor: 80
};
const wizard: Mage = {
name: "Merlin",
health: 80,
mana: 200,
spells: ["fireball", "ice storm"]
};
Type aliases can also define object shapes. Choose based on your needs:
// Type alias (similar to interface)
type Point = {
x: number;
y: number;
};
// Type alias can also define unions
type Status = "active" | "inactive" | "pending";
// Type alias for function signature
type ClickHandler = (event: MouseEvent) => void;
// When to use interfaces vs type aliases:
// - Use INTERFACE for object shapes (extendable, better error messages)
// - Use TYPE for unions, intersections, and complex types
// Intersection with type aliases
type Named = { name: string };
type Aged = { age: number };
type Person = Named & Aged; // Combines both types
const person: Person = {
name: "Alice",
age: 30
};
Interfaces can include method signatures:
interface Inventory {
items: string[];
capacity: number;
// Method signatures
addItem(item: string): boolean;
removeItem(item: string): boolean;
isFull(): boolean;
}
// Implementing the interface
class PlayerInventory implements Inventory {
items: string[] = [];
capacity: number = 20;
addItem(item: string): boolean {
if (this.items.length < this.capacity) {
this.items.push(item);
return true;
}
return false;
}
removeItem(item: string): boolean {
const index = this.items.indexOf(item);
if (index > -1) {
this.items.splice(index, 1);
return true;
}
return false;
}
isFull(): boolean {
return this.items.length >= this.capacity;
}
}
const backpack = new PlayerInventory();
backpack.addItem("sword");
backpack.addItem("potion");
? for optional propertiesreadonly for properties that shouldn't change