On this page · sections
Interfaces vs Type Aliases
Both interface and type describe shapes in TypeScript. They overlap heavily for object contracts — the difference shows up in extension, unions, declaration merging, and advanced type expressions.
| Feature | interface | type |
|---|---|---|
| Object shapes | ✅ | ✅ |
| Extend / implement | ✅ extends | ✅ & intersection |
| Unions / primitives | ❌ | ✅ |
| Declaration merging | ✅ | ❌ |
| Mapped / conditional types | ❌ | ✅ |
implements in classes | ✅ | ✅ (alias only) |
| Tuple types | ❌ | ✅ |
interface
Definition
An interface declares a named object shape. Multiple declarations with the same name merge into one type.
interface User {
id: string;
}
interface User {
name: string;
}
// Merged: { id: string; name: string }
const user: User = { id: "1", name: "Ada" };Real-world example — extending library types
Declaration merging augments third-party types without forking:
// global.d.ts
interface Window {
analytics?: { track: (event: string) => void };
}
window.analytics?.track("page_view");Express / Node augmentation follows the same pattern:
// types/express.d.ts
declare global {
namespace Express {
interface Request {
userId?: string;
}
}
}Class implementation
interface Serializable {
toJSON(): string;
}
class User implements Serializable {
constructor(public name: string) {}
toJSON() {
return JSON.stringify({ name: this.name });
}
}Interview: Use
interfacewhen you expect extension — OOP contracts, library augmentation, team-facing public APIs.
type
Definition
A type alias names any type — objects, unions, primitives, tuples, mapped types, and conditional types.
type ID = string | number;
type Status = "idle" | "loading" | "success" | "error";
type Point = [number, number];
type User = {
id: ID;
status: Status;
};Unions and discriminated unions
type Result<T> =
| { ok: true; data: T }
| { ok: false; error: string };
function handle<T>(result: Result<T>) {
if (result.ok) {
return result.data;
}
throw new Error(result.error);
}Interview: Use
typefor unions, tuples, utility compositions, and anything that isn’t a plain extendable object contract.
Object shapes — when both work
For a simple props object, either keyword is valid:
interface ButtonProps {
label: string;
onClick: () => void;
}
type ButtonProps = {
label: string;
onClick: () => void;
};Extension syntax
interface BaseProps {
className?: string;
}
interface ButtonProps extends BaseProps {
label: string;
}
type BaseProps = { className?: string };
type ButtonProps = BaseProps & { label: string };Behavior is equivalent for object composition. Pick one style per codebase and stay consistent.
Multiple extension
interface Identifiable {
id: string;
}
interface Timestamped {
createdAt: Date;
}
interface Entity extends Identifiable, Timestamped {
name: string;
}
type Entity = Identifiable & Timestamped & { name: string };When the distinction actually matters
1. Declaration merging — interface only
interface Env {
API_URL: string;
}
interface Env {
DEBUG: boolean;
}
// Env = { API_URL: string; DEBUG: boolean }type cannot be re-opened. Duplicate type Env declarations are a compile error.
2. Unions — type only
type Theme = "light" | "dark" | "system";
type Route = "/" | "/settings" | `/users/${string}`;Interfaces cannot express primitive unions directly.
3. Mapped / conditional types — type only
type Readonly<T> = {
readonly [K in keyof T]: T[K];
};
type NonNullable<T> = T extends null | undefined ? never : T;4. Computed / indexed mapped shapes
type Flags<T extends string> = {
[K in T]: boolean;
};
type FeatureFlags = Flags<"beta" | "darkMode">;
// { beta: boolean; darkMode: boolean }extends vs intersection (&)
Definition
Both compose types, but errors and display differ:
interface A { x: number }
interface B extends A { y: string }
type C = A & { y: string }; // similar to BConflict behavior
type Conflict = { x: number } & { x: string };
// x: never — incompatible intersection
interface Bad extends { x: number }, { x: string } {}
// ❌ Compiler error on extends — interfaces catch conflicts earlierInterview: Interfaces give clearer errors on incompatible property merges. Intersections can silently produce
neveron conflicting keys.
React and frontend conventions
Component props
// Common in design systems — interface for public props
interface CardProps {
title: string;
children: React.ReactNode;
variant?: "default" | "outlined";
}
// Common for state / results — type for unions
type AsyncState<T> =
| { status: "idle" }
| { status: "loading" }
| { status: "success"; data: T }
| { status: "error"; error: Error };Extending HTML element props
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
loading?: boolean;
}
type InputProps = React.InputHTMLAttributes<HTMLInputElement> & {
label: string;
};Both patterns are idiomatic. interface extends reads well for component libraries.
Team conventions (senior answer)
Public component props API → interface (extendable, familiar to consumers)
Union / Result / State → type
Utility / mapped types → type
Augmenting third-party → interface (declaration merging)
Tuple / branded IDs → typeSenior Interview Summary
interface
Named object contract; supports
extendsand declaration merging. Best for public APIs, classimplements, and library augmentation.
type
Alias for any type expression — objects, unions, tuples, mapped types. Required for unions and advanced type logic.
Best mental model
interface → "open, mergeable object contract"
type → "name for any type expression"Practical rule
Plain object props → either (be consistent)
Unions / discriminated → type
Augment Window/Express → interface
Mapped utilities → type
Conflicting keys → prefer interface extends for early errors