Difficulty: Intermediate
Interfaces are named object type definitions in TypeScript. They describe the shape of an object by listing its properties and their types. The syntax uses the `interface` keyword: `interface User { name: string; age: number; }`. Unlike inline object types, interfaces are reusable, can be extended, and support declaration merging - making them the primary tool for defining object shapes in TypeScript.
Interfaces support all the property modifiers available for object types: optional properties (`?`), readonly properties (`readonly`), and method signatures. A method can be declared in shorthand: `greet(name: string): string` or as a property with a function type: `greet: (name: string) => string`. Both are equivalent in practice, though the shorthand form is more conventional in interfaces.
Index signatures allow an interface to describe objects with dynamic keys. The syntax is `[key: string]: ValueType`, which means the object can have any string key mapping to the specified value type. This is useful for dictionaries, caches, and configuration objects where the exact keys aren't known in advance. You can combine index signatures with known properties, but the known properties must be compatible with the index signature's value type.
Interfaces are open, meaning they can be declared multiple times with the same name and TypeScript automatically merges them. This is called declaration merging. It's particularly useful for extending third-party types - you can add properties to an existing interface from a library without modifying the library's source code. This feature is unique to interfaces and is one of the key differences from type aliases.
interface User {
readonly id: number;
name: string;
email: string;
age?: number;
getDisplayName(): string;
}
function createUser(name: string, email: string): User {
const id = Math.floor(Math.random() * 1000);
return {
id,
name,
email,
getDisplayName() {
return `${this.name} <${this.email}>`;
}
};
}
const user = createUser("Alice", "alice@example.com");
console.log(user.getDisplayName());
console.log(`ID: ${user.id}`);
Interfaces combine property definitions and method signatures. The `readonly` modifier on `id` prevents reassignment. Optional `age` can be omitted. The method `getDisplayName()` uses shorthand syntax.
interface StringDictionary {
[key: string]: string;
}
interface ScoreBoard {
[player: string]: number;
readonly gameId: number; // Known property, compatible with index
}
const headers: StringDictionary = {
"Content-Type": "application/json",
"Authorization": "Bearer token123",
"Accept": "text/html"
};
const scores: ScoreBoard = {
gameId: 1,
Alice: 95,
Bob: 87,
Charlie: 92
};
console.log(headers["Content-Type"]);
console.log(`Alice: ${scores["Alice"]}, Bob: ${scores["Bob"]}`);
Index signatures allow dynamic keys. StringDictionary accepts any string key with string values. ScoreBoard combines an index signature with a known `gameId` property.
// First declaration
interface Config {
appName: string;
version: string;
}
// Second declaration - merges with the first
interface Config {
debug: boolean;
logLevel: string;
}
// The merged interface has all four properties
const config: Config = {
appName: "MyApp",
version: "1.0.0",
debug: true,
logLevel: "info"
};
console.log(`${config.appName} v${config.version}`);
console.log(`Debug: ${config.debug}, Log: ${config.logLevel}`);
Declaration merging automatically combines multiple interface declarations with the same name. This is unique to interfaces - type aliases cannot merge. It's useful for extending library types.
Interface declaration, Readonly properties, Index signatures, Method signatures