Syntax Cache
BlogMethodFeaturesHow It WorksBuild a Game
  1. Home
  2. Cheat Sheets
  3. TypeScript
  4. TypeScript Interface vs Type Cheat Sheet
TypeScriptCheat Sheet

TypeScript Interface vs Type Cheat Sheet

Side-by-side comparison of interface and type alias syntax, capabilities, and trade-offs -- with a quick decision table.

On this page
  1. 1Basic Syntax Comparison
  2. 2When to Use Interface
  3. 3When to Use Type
  4. 4Object Shapes (Both Work)
  5. 5Extending and Composing
  6. 6Declaration Merging
  7. 7TS 5 Behavior Changes
  8. 8Quick Decision Table
Basic Syntax ComparisonWhen to Use InterfaceWhen to Use TypeObject Shapes (Both Work)Extending and ComposingDeclaration MergingTS 5 Behavior ChangesQuick Decision Table

Basic Syntax Comparison

Both can define object shapes. The syntax differs, but the result is often identical.

Interface syntax
interface User {
  id: number;
  name: string;
  email: string;
}

const user: User = { id: 1, name: 'Alice', email: 'alice@example.com' };
Type alias syntax
type User = {
  id: number;
  name: string;
  email: string;
};

const user: User = { id: 1, name: 'Alice', email: 'alice@example.com' };

For plain object shapes, these produce the same result. The choice is a style preference.

When to Use Interface

Interface wins when you need `extends` inheritance or declaration merging.

Extending interfaces
interface Animal {
  name: string;
  age: number;
}

interface Dog extends Animal {
  breed: string;
}

// Dog has: name, age, breed
const dog: Dog = { name: 'Rex', age: 3, breed: 'Labrador' };
Multi-interface extension
interface HasId { id: string }
interface HasTimestamps { createdAt: Date; updatedAt: Date }

interface Article extends HasId, HasTimestamps {
  title: string;
  body: string;
}

// Article has: id, createdAt, updatedAt, title, body
Declaration merging (interface only)
// First declaration
interface Window {
  title: string;
}

// Second declaration -- merges with the first
interface Window {
  appVersion: string;
}

// Window now has both: title and appVersion
const w: Window = { title: 'My App', appVersion: '1.0.0' };

Declaration merging is how libraries add properties to global types. Type aliases can't do this.

When to Use Type

Type aliases handle unions, primitives, tuples, and mapped types -- things interfaces can't express.

Union types
type Status = 'pending' | 'active' | 'archived';

type Result<T> =
  | { ok: true; data: T }
  | { ok: false; error: string };
Primitive and tuple types
type ID = string | number;
type Coordinate = [x: number, y: number];
type Callback = (event: Event) => void;

// None of these can be expressed with interface
Mapped types
type Flags<T> = {
  [K in keyof T]: boolean;
};

interface Features {
  darkMode: string;
  notifications: string;
  analytics: string;
}

type FeatureFlags = Flags<Features>;
// => { darkMode: boolean; notifications: boolean; analytics: boolean }

Mapped types use the `in` keyword to iterate over keys. This only works with type aliases.

Object Shapes (Both Work)

For plain object types, interface and type are interchangeable. Pick a convention and stick with it.

Optional and readonly properties
// Interface version
interface ConfigI {
  readonly apiUrl: string;
  timeout?: number;
  debug?: boolean;
}

// Type version -- identical behavior
type ConfigT = {
  readonly apiUrl: string;
  timeout?: number;
  debug?: boolean;
};
Index signatures
// Interface
interface StringMap {
  [key: string]: string;
}

// Type
type StringMapT = {
  [key: string]: string;
};

// Both accept the same values
const headers: StringMap = { 'Content-Type': 'application/json' };
Method signatures
// Interface
interface Formatter {
  format(value: string): string;
}

// Type
type FormatterT = {
  format(value: string): string;
};

// Both accept the same implementation
const upper: Formatter = { format: (v) => v.toUpperCase() };

Extending and Composing

Interfaces use `extends`. Types use intersection (`&`). Both combine types, but error messages differ.

Interface extends
interface Base { id: number }
interface WithName extends Base { name: string }

const item: WithName = { id: 1, name: 'Alice' };
Type intersection
type Base = { id: number };
type WithName = Base & { name: string };

const item: WithName = { id: 1, name: 'Alice' };
Mixing interface and type
// Type extending an interface
interface Animal { name: string }
type Pet = Animal & { owner: string };

// Interface extending a type
type HasId = { id: number };
interface User extends HasId { name: string }

Interfaces and types interoperate freely. An interface can extend a type alias and vice versa.

Conflict behavior differs
// Interface extends -- conflicts are caught:
interface A { x: number }
// interface B extends A { x: string }
// => Error: 'x' in B is incompatible with 'x' in A

// Type intersection -- conflicts produce never:
type C = { x: number } & { x: string };
// C.x is type: never (number & string = nothing)

Interface extends gives a clear error on property conflicts. Intersection silently produces `never` for the conflicting property -- harder to debug.

Declaration Merging

Interfaces with the same name merge their declarations automatically. Type aliases can't do this.

Augmenting third-party types
// In your app code, extend Express Request:
declare module 'express' {
  interface Request {
    userId?: string;
    sessionId?: string;
  }
}

// Now req.userId is available in handlers
// This is the primary real-world use of declaration merging
Augmenting global types
// Add a custom property to the global Window
declare global {
  interface Window {
    analytics: {
      track(event: string, data?: Record<string, unknown>): void;
    };
  }
}

// Now window.analytics.track('pageview') type-checks
Type aliases can't merge
type Config = { host: string };
// type Config = { port: number };
// => Error: Duplicate identifier 'Config'

// If you need merging, use interface instead:
interface Config { host: string }
interface Config { port: number }
// Config now has both host and port

TS 5 Behavior Changes

TypeScript 5 narrowed some differences between interfaces and types.

Faster intersection handling in TS 5.0+
// TS 5.0 improved performance for intersection types.
// Large intersections that caused slowdowns in TS 4.x are now faster.

// Before (TS 4.x): complex intersections could cause slow type-checking
type BigIntersection = A & B & C & D & E & F;  // could stall

// After (TS 5.0+): same type checks faster due to
// internal deferred type evaluation
Better error messages for type mismatches
// TS 5.x produces clearer error messages for both:

interface UserI { name: string; age: number }
type UserT = { name: string; age: number };

// Both now show structured diffs on mismatch:
// const user: UserI = { name: 'Alice', age: 'thirty' };
//                                       ~~~~~~~~~~~
// Type 'string' is not assignable to type 'number'.

TS 5 made error messages more consistent between interface and type alias mismatches. This was a historical difference that no longer matters.

Quick Decision Table

Use this table when you're unsure which to pick.

When to use interface vs type
// Use INTERFACE when:
//   - Defining an object shape you might extend later
//   - You need declaration merging (augmenting libraries)
//   - You want clearer error messages on property conflicts
//   - Your team convention says "interface for objects"

// Use TYPE when:
//   - Defining a union type (A | B)
//   - Defining a tuple type ([string, number])
//   - Defining a primitive alias (type ID = string)
//   - Using mapped types or conditional types
//   - Composing types with intersection (&)
//   - Your team convention says "type for everything"

// Use EITHER when:
//   - Defining a plain object shape (both work identically)
//   - The decision doesn't matter (be consistent, pick one)
Common team conventions
// Convention A: "interface for objects, type for everything else"
interface User { name: string }          // object shape
type Status = 'active' | 'inactive';     // union
type Pair = [string, number];            // tuple

// Convention B: "type for everything"
type User = { name: string };
type Status = 'active' | 'inactive';
type Pair = [string, number];

// Both are valid. The TypeScript team uses Convention A internally.

Consistency within a codebase matters more than the specific choice. Use ESLint's consistent-type-definitions rule to enforce your convention.

Learn TypeScript in Depth
Interfaces vs Types →
Warm-up1 / 1

Can you write this from memory?

Create Admin interface extending User with role: string

See Also
Generics →Utility Types →Union & Intersection Types →

Start Practicing TypeScript

Free daily exercises with spaced repetition. No credit card required.

← Back to TypeScript Syntax Practice
Syntax Cache

Build syntax muscle memory with spaced repetition.

Product

  • Pricing
  • Our Method
  • Daily Practice
  • Design Patterns
  • Interview Prep

Resources

  • Blog
  • Compare
  • Cheat Sheets
  • Vibe Coding
  • Muscle Memory

Languages

  • Python
  • JavaScript
  • TypeScript
  • Rust
  • SQL
  • GDScript

Legal

  • Terms
  • Privacy
  • Contact

© 2026 Syntax Cache

Cancel anytime in 2 clicks. Keep access until the end of your billing period.

No refunds for partial billing periods.