Topics
v5.0.0
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.

Featureinterfacetype
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 interface when 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 type for 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 B

Conflict 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 earlier

Interview: Interfaces give clearer errors on incompatible property merges. Intersections can silently produce never on 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 → type

Senior Interview Summary

interface

Named object contract; supports extends and declaration merging. Best for public APIs, class implements, 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

Last updated on