Type vs Interface vs Class vs Tuple (and When to Use Each)

TypeScript gives you many ways to describe data.

That’s powerful, but also confusing.

If you’ve ever asked “why does this exist when that already works”, this post is for you.

I’ll explain what each one really is, what it’s good at, and when not to use it.


Mental model first (important)

  • type / interface → describe shape
  • class → describes shape + behavior
  • tuple → describes fixed-position data
  • enum / union / literal → describe allowed values

If you mix these roles, your code becomes hard to reason about.


type

What it is

A type alias.

It gives a name to a shape, union, or primitive.

type User = {
  id: number
  name: string
}

What makes type powerful

  • Can represent almost anything
  • Supports unions, intersections, tuples, primitives
type Status = 'idle' | 'loading' | 'success' | 'error'
type ApiResult<T> = T | null

Limitations

  • Cannot be merged
  • Cannot be extended the same way as interfaces (only via intersections)

interface What it is

A contract for object shape, designed for extension.

interface User {
  id: number
  name: string
}

Why interfaces exist

They were designed for:

  • public APIs
  • shared contracts
  • libraries

Special feature: declaration merging

interface User {
  id: number
}

interface User {
  name: string
}

Becomes:

interface User {
  id: number
  name: string
}

This is useful in frameworks, dangerous in apps.


type VS interface  (honest comparison)

CaseUse
Simple object shapeEither
Public APIinterface
Unions / literalstype
Advanced typestype
Declaration merging neededinterface
You want strictnesstype

Rule I recommend:

Use type by default.

Use interface only when you need extension or public contracts.


class

What it is

A runtime object, not just a type.

class User {
  constructor(
    public id: number,
    public name: string
  ) {}

  greet() {
    return `Hi ${this.name}`
  }
}

Key difference

  • type and interface disappear at runtime
  • class exists in JavaScript

That means:

  • memory
  • methods
  • behavior

When to use class

Use class when:

  • you need behavior
  • you need lifecycle
  • you need inheritance
  • you need instanceof

Example:

class BasePageObject {
  constructor(protected page: Page) {}
}

This cannot be replaced by type.


Common mistake (don’t do this)

Using class just to type data:

class User {
  id: number
  name: string
}

That’s wrong. Use type instead.


tuple

What it is

A fixed-length, ordered array with specific types per position.

type Point = [number, number]

Usage:

const point: Point = [10, 20]

Why tuples exist

They model positional meaning.

Example:

type ApiResponse = [data: User, status: number]

Order matters. Shape alone is not enough.


Tuple vs array

number[]      // any length, any order
[number, number] // exactly two values, fixed order

Use tuples sparingly. Overusing them hurts readability.


Other important ones you should know

Union types

type Result = 'success' | 'error'

Use when values are limited.


Intersection types

type Admin = User & { role: 'admin' }

Combine shapes.


Literal types

type Direction = 'left' | 'right'

Great for configs and states.


Enum (honest take)

enum Status {
  Idle,
  Loading,
}

Avoid unless you need runtime values.

Unions are usually better and safer.


Quick decision guide

  • Describing data shape → type
  • Describing public contract → interface
  • Describing behavior → class
  • Fixed ordered data → tuple
  • Limited values → union
  • Runtime object needed → class

One sentence summary

Types describe data.

Classes create things.

Tuples describe positions.

Once you stop mixing those ideas, TypeScript becomes much simpler.