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)
| Case | Use |
|---|---|
| Simple object shape | Either |
| Public API | interface |
| Unions / literals | type |
| Advanced types | type |
| Declaration merging needed | interface |
| You want strictness | type |
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.