If you've been working with TypeScript for a while, you've probably come across both type
aliases and interface
declarations. At first glance, they might seem pretty similar - they both help you define the shape of your data. But when should you use one over the other? Let's break it down in simple terms.
What Are They Anyway?
Think of both types and interfaces as blueprints for your data. They tell TypeScript what properties an object should have and what types those properties should be.
Interfaces - The Classic Approach
Interfaces have been around since the early days of TypeScript. They're great for describing the structure of objects:
1interface User {
2name: string;
3age: number;
4email?: string; // The ? makes this optional
5}
6
7const user: User = {
8name: "Sarah",
9age: 28,
10email: "sarah@example.com"
11};
Type Aliases - The Flexible Option
Type aliases came later and offer more flexibility. You can use them for objects, but also for other things:
1type User = {
2name: string;
3age: number;
4email?: string;
5};
6
7// But you can also do this with types:
8type Status = "loading" | "success" | "error";
9type ID = string | number;
10type Coordinates = [number, number];
The Main Differences
1. What They Can Define
Interfaces are mainly for objects and classes:
1interface Animal {
2name: string;
3makeSound(): void;
4}
Types can define pretty much anything:
1// Objects (just like interfaces)
2type Animal = {
3name: string;
4makeSound(): void;
5};
6
7// Union types
8type Theme = "light" | "dark";
9
10// Primitives
11type UserID = string;
12
13// Tuples
14type Point = [number, number];
15
16// Functions
17type Calculator = (a: number, b: number) => number;
2. Extending and Combining
Interfaces use the extends
keyword, which feels natural:
1interface Animal {
2name: string;
3}
4
5interface Dog extends Animal {
6breed: string;
7bark(): void;
8}
Types use intersection (&
) to combine:
1type Animal = {
2name: string;
3};
4
5type Dog = Animal & {
6breed: string;
7bark(): void;
8};
3. Declaration Merging (This One's Important!)
Here's where interfaces have a unique superpower. If you declare the same interface twice, TypeScript automatically merges them:
1interface User {
2name: string;
3}
4
5interface User {
6age: number;
7}
8
9// TypeScript sees this as:
10// interface User {
11// name: string;
12// age: number;
13// }
This is super useful for extending third-party libraries or adding properties across different files.
Types don't do this - you'll get an error if you try to declare the same type twice:
1type User = {
2name: string;
3};
4
5type User = { // Error! Duplicate identifier
6age: number;
7};
So Which Should You Use?
Here's my practical advice:
Use Interfaces When:
- You're defining object shapes - especially for things like API responses, component props, or database models
- You might need to extend them later - interfaces play nice with inheritance
- You're working with classes - interfaces work great with the
implements
keyword - You're building a library - declaration merging can be helpful for users
1interface APIResponse {
2status: number;
3data: any;
4}
5
6interface UserProps {
7user: User;
8onEdit: (user: User) => void;
9}
10
11class DatabaseConnection implements ConnectionInterface {
12// implementation here
13}
Use Types When:
- You need union types - like
"loading" | "success" | "error"
- You're working with primitives - like
type UserID = string
- You need computed properties - types are more flexible with advanced TypeScript features
- You're defining function signatures - types feel more natural here
1type Theme = "light" | "dark" | "auto";
2type EventHandler = (event: Event) => void;
3type UserID = string;
4type APIStatus = "idle" | "loading" | "success" | "error";
Real-World Example
Let's say you're building a user management system. Here's how you might use both:
1// Use interface for the main user object
2interface User {
3id: string;
4name: string;
5email: string;
6createdAt: Date;
7}
8
9// Use types for specific values
10type UserRole = "admin" | "user" | "guest";
11type UserStatus = "active" | "inactive" | "banned";
12
13// Extend the interface when needed
14interface AdminUser extends User {
15role: UserRole;
16permissions: string[];
17}
18
19// Use types for function signatures
20type UserUpdater = (id: string, updates: Partial<User>) => Promise<User>;
21type UserValidator = (user: User) => boolean;
The Bottom Line
Don't stress too much about making the "perfect" choice every time. Both types and interfaces are great tools, and in many cases, they're interchangeable. The TypeScript team even says they recommend using interfaces until you need the specific features that types provide.
Start with interfaces for object shapes and use types when you need unions, primitives, or more advanced features. As you get more comfortable with TypeScript, you'll develop your own preferences and patterns.
The most important thing is to be consistent within your project and team. Pick an approach and stick with it!
Quick Reference
Feature | Interface | Type |
---|---|---|
Object shapes | ✅ | ✅ |
Union types | ❌ | ✅ |
Primitives | ❌ | ✅ |
Extending | `extends` keyword | `&` intersection |
Declaration merging | ✅ | ❌ |
Classes | Works great with `implements` | Can work, but less common |
Happy coding! 🚀