Interfaces in TypeScript
Describing the shape of an object with interface, including extending one interface from another.
2 min read
An interface names the shape of an object — what properties it has and what type each one is — so you can reuse that shape across functions and variables instead of repeating an inline object type everywhere.
Defining and using an interface
interface User {
name: string;
age: number;
isActive: boolean;
}
const user: User = {
name: "Ada",
age: 36,
isActive: true,
};Any object assigned to a User-typed variable must have exactly the properties the interface describes (with the right types) — missing a property, misspelling one, or using the wrong type is a compile error.
const bad: User = { name: "Ada", age: 36 }; // Property 'isActive' is missingUsing an interface as a function parameter type
This is where interfaces pay off most — a function can describe exactly what shape of object it expects, and TypeScript checks every call:
function printUser(user: User): void {
console.log(`${user.name} (${user.age})`);
}
printUser({ name: "Grace", age: 45, isActive: false }); // fine
printUser({ name: "Grace" }); // missing age and isActiveExtending an interface
interface Admin extends User {
permissions: string[];
}
const admin: Admin = {
name: "Ada",
age: 36,
isActive: true,
permissions: ["read", "write", "delete"],
};extends gives Admin every property of User plus its own — a common pattern for modeling a more specific version of a general shape, without repeating the shared fields.
Interfaces can also describe function types and index signatures
interface Calculator {
(a: number, b: number): number; // callable
}
const add: Calculator = (a, b) => a + b;
interface StringMap {
[key: string]: string; // any string key maps to a string value
}
const colors: StringMap = { primary: "#3178c6", secondary: "#f7df1e" };Declaration merging: a behavior unique to interfaces
Declaring the same interface name twice doesn't overwrite the first — TypeScript merges both declarations into one combined shape:
interface User {
name: string;
}
interface User {
age: number;
}
const user: User = { name: "Ada", age: 36 }; // needs both -- they mergedThis is genuinely useful for extending a type from a library without modifying its source, but it also means two same-named interfaces in different files silently combine instead of conflicting — worth knowing before it surprises you. type aliases, covered soon, don't support this at all — redeclaring one is a straightforward error.
The next lesson looks directly at interface next to its close relative type, and where the real, non-obvious differences between them are.