Suggest an editImprove this articleRefine the answer for “Differences between type and interface in TypeScript”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**`type` vs `interface`** in TypeScript: `interface` supports declaration merging and `extends` inheritance; `type` supports union types, primitives, and type transformations. ```typescript interface User { name: string } interface User { age: number } // merges automatically type Status = "active" | "inactive"; // only type supports this type ID = string | number; // primitive alias ``` **Key:** use `interface` for object contracts you might extend; use `type` for unions, primitives, and anything that is not a plain object shape.Shown above the full answer for quick recall.Answer (EN)Image**`type` and `interface`** in TypeScript both describe object shapes, but with one key behavioral difference: `interface` merges repeated declarations, while `type` creates a fixed alias that works with unions, primitives, and mapped types. ## Theory ### TL;DR - `interface` = Google Doc (you can add properties later); `type` = sealed PDF (fixed at creation) - Main difference: `interface` merges repeated declarations automatically; `type` throws an error on redeclaration - Only `type` supports union types (`A | B`), primitives, tuples, and mapped types - Need object contracts, class inheritance, or library augmentation? `interface`. Need unions, discriminated unions, or type transformations? `type` - Both are fully erased at compile time. JavaScript sees neither ### Quick example ```typescript // interface: repeated declarations merge interface User { name: string; } interface User { age: number; // merges with the declaration above } // Result: User = { name: string; age: number } // type: cannot be redeclared type Product = { id: number }; // type Product = { name: string }; // ERROR: Duplicate identifier 'Product' // Only type supports union types type Status = "active" | "inactive" | "pending"; type Result = User | Product; ``` Declare `User` twice with `interface` and TypeScript combines both into one. Do the same with `type` and the compiler refuses. ### Key difference `interface` is for object shapes. It supports declaration merging, where two declarations with the same name combine into one, and inheritance via `extends`. `type` is an alias for any structure: primitives, union types, intersections, tuples. But it cannot be merged or redeclared. Once you write `type X = ...`, that definition is final. ### When to use - **`interface`:** object contracts for classes (`implements`), API response shapes, React props that other components might extend, library augmentation where consumers need to add properties - **`type`:** union types (`"success" | "error"`), intersections (`A & B`), primitive aliases (`type ID = string | number`), discriminated unions for state machines, mapped types, conditional types Many teams default to `interface` for objects and `type` for everything else. Others use `type` everywhere. Both approaches work. The biggest friction I've seen: someone ships a public library with `type` for contracts that consumers need to extend. That choice ripples through every downstream project. ### Comparison table | Feature | `interface` | `type` | |---|---|---| | Object shape | Yes | Yes | | Union types | No | Yes | | Intersection | Via `extends` | Via `&` | | Declaration merging | Yes | No | | Primitives / tuples | No | Yes | | Function signatures | Works, not conventional | Yes | | Mapped types | No | Yes | | Conditional types | No | Yes | | `implements` in classes | Yes (conventional) | Yes (works) | | **Best for** | Object contracts, class APIs, library augmentation | Unions, primitives, type transformations | ### How the compiler handles this TypeScript treats `interface` declarations as open containers. When it sees two declarations with the same name, it merges their properties into one type. `type` aliases are direct substitutions: the compiler replaces each reference with its definition. Neither survives compilation. Both are fully erased when TypeScript outputs JavaScript. ### Common mistakes **Trying to create a union with `interface`** ```typescript // WRONG - syntax error // interface Result = User | Product; // RIGHT type Result = User | Product; ``` `interface` cannot express "either A or B". That belongs to `type`. --- **Expecting `type` to merge like `interface`** ```typescript // WRONG type Config = { apiUrl: string }; type Config = { timeout: number }; // ERROR: Duplicate identifier // RIGHT - use interface when merging is needed interface Config { apiUrl: string; } interface Config { timeout: number; } // merges automatically const config: Config = { apiUrl: "...", timeout: 5000 }; // works ``` --- **Using optional properties instead of a discriminated union** ```typescript // WRONG - allows impossible state combinations interface State { status: "idle" | "loading" | "success" | "error"; data?: User; error?: string; // nothing prevents data and error coexisting } // RIGHT - discriminated union makes invalid states unrepresentable type State = | { status: "idle" } | { status: "loading" } | { status: "success"; data: User } | { status: "error"; error: string }; ``` This pattern requires `type`. No equivalent exists with `interface`. --- **Using `&` instead of `extends` for object inheritance** ```typescript // Works, but hides the hierarchy type AdvancedConfig = BaseConfig & { ssl: boolean }; // Clearer when the relationship matters interface AdvancedConfig extends BaseConfig { ssl: boolean; } ``` There is a real behavioral difference here. `extends` catches property conflicts immediately with a compile error. With `&`, a conflicting property type resolves to `never` instead, a silent dead end that produces no error until you try to use the property. --- **Using `interface` for function type signatures** ```typescript // Works, but uncommon interface Callback { (error: Error | null, data: string): void; } // Cleaner and standard type Callback = (error: Error | null, data: string) => void; ``` ### Real-world usage - **React:** props interfaces extend `React.PropsWithChildren`; component state uses discriminated `type` unions - **Redux:** action types use discriminated `type` unions for type-safe reducers; store shape uses `interface` - **Express:** middleware adds properties to `Request` via `interface` declaration merging; route handlers use `type` for response unions - **NestJS:** DTOs use `interface` for inheritance; API responses use `type` for discriminated unions - **Library authors:** all public contracts exported as `interface` so consumers can augment them without modifying source ### Follow-up questions **Q:** Can you use `type` in a class `implements` statement? **A:** Yes. It works structurally. But `interface` is the conventional choice because it signals intent and supports declaration merging. Using `type` with `implements` is not wrong, just less standard. **Q:** What happens when you extend an `interface` with a conflicting property? **A:** TypeScript reports the error immediately at the `extends` statement. With `&` in `type`, conflicting property types resolve to `never` instead. The practical difference: `never` is silent, `extends` gives you an error right at the source. **Q:** How does declaration merging work with generics? **A:** Generic parameters must match exactly across merged declarations. Two `interface Box<T>` declarations merge fine. But `interface Box<T, U>` and `interface Box<T>` are treated as different interfaces and will not merge. **Q:** Why do Express, NestJS, and Fastify use `interface` for their public types? **A:** Because consumers need to extend them. Express middleware adds custom properties to `Request` by redeclaring the `interface` locally. If Express exported `type Request = { ... }`, that augmentation would not compile. **Q:** (Senior) How would you design a public type system where consumers can extend types without touching your source? **A:** Export all public contracts as `interface`. This lets consumers use declaration merging in their own files by adding properties through redeclaration. If you used `type`, they are limited to exactly what you shipped. This is exactly how Express `Request` augmentation works. ## Examples ### Object extension with `interface` ```typescript interface BaseProps { className?: string; children: React.ReactNode; } interface ButtonProps extends BaseProps { onClick: () => void; variant: "primary" | "secondary"; } const Button: React.FC<ButtonProps> = ({ onClick, variant, children, className, }) => ( <button onClick={onClick} className={`btn btn-${variant} ${className}`}> {children} </button> ); ``` `ButtonProps` picks up `className` and `children` from `BaseProps` automatically. The `extends` keyword makes the relationship explicit and catches conflicts at compile time. ### Discriminated union with `type` (API state machine) ```typescript type ApiResponse = | { status: "success"; data: User[] } | { status: "error"; error: string } | { status: "loading" }; function handleResponse(response: ApiResponse) { switch (response.status) { case "success": console.log(response.data); // TypeScript knows data exists here break; case "error": console.error(response.error); // TypeScript knows error exists here break; case "loading": console.log("Waiting..."); break; } } ``` TypeScript uses the `status` field to narrow the type in each branch. Add a new status variant and forget to handle it - TypeScript catches the gap. ### Declaration merging for library augmentation ```typescript declare global { namespace Express { interface Request { user?: { id: string; role: "admin" | "viewer"; }; } } } app.get("/profile", (req, res) => { if (req.user?.role === "admin") { // TypeScript knows user.role has exactly two possible values } }); ``` This works because Express exports `Request` as an `interface`. The `declare global` block merges additions into the existing definition. With `type`, this does not compile.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.