Quick Overview
Two `interface` declarations with the same name merge; two `type` aliases collide. That single behaviour decides the choice: merging is what makes interfaces right for public APIs others need to extend.
TypeScript provides two primary keywords for defining object shapes: interface and type. While they overlap significantly, they possess distinct capabilities.
extends)An interface defines an object contract that can be extended using the extends keyword:
interface User {
id: number;
name: string;
}
// Extending an interface
interface AdminUser extends User {
permissions: string[];
}
const admin: AdminUser = {
id: 1,
name: "Sarah",
permissions: ["read", "write", "delete"],
};
Multiple interface declarations with the same name in the same scope automatically merge their property definitions:
interface Window {
customTitle: string;
}
interface Window {
customVersion: number;
}
// Window interface now contains BOTH properties!
&)Type aliases use Intersection Types (&) to combine object shapes:
type AuditLog = { timestamp: Date };
type UserLog = User & AuditLog; // Intersection combining shapes
| Feature | Interface | Type Alias |
|---|---|---|
| Object Shapes | Supported | Supported |
| Inheritance Syntax | extends | Intersection (&) |
| Declaration Merging | Yes (Auto-merges) | No (Duplicate identifier error) |
| Unions & Primitives | No | Yes (`type Status = "ok" |
type User = {...} twice throws a duplicate identifier compiler error. Use interfaces when library authors need expandable shapes!Default to interface for OOP/library object shapes, and type for unions and utility types. Next, let's explore Union Types and Type Narrowing!
TypeScript erases entirely at build time, so a `string` annotation produces no runtime check whatsoever. A value arriving from an API is whatever the server actually sent, regardless of what the annotation claims.
`let x = "GET"` infers `string` because the binding can change; `const x = "GET"` infers the literal `"GET"`. That widening rule is why object properties need `as const` to stay narrow.
`string[]` is any number of strings; `[string, number]` is exactly two, in that order. `readonly` removes the mutating methods at compile time, so `push` becomes a type error rather than a runtime surprise.