Welcome to The Coding College, where we simplify programming concepts for developers of all skill levels. At The Coding College, our mission is to empower you with knowledge to write clean and efficient code. In today’s post, we’ll explore Type Aliases and Interfaces in TypeScript, highlighting their differences, use cases, and best practices.
What Are Type Aliases and Interfaces in TypeScript?
Both type aliases and interfaces are tools for defining custom types in TypeScript. They allow you to describe the structure of an object, a function, or even more complex constructs. While they often overlap in functionality, each has its unique strengths and use cases.
Type Aliases
A type alias is a way to create a custom type with a name. You can use it for objects, primitives, unions, intersections, and more.
Syntax
type AliasName = definition;
Example: Basic Object
type User = {
name: string;
age: number;
isActive: boolean;
};
let user: User = { name: "Alice", age: 30, isActive: true };
console.log(user.name); // Output: Alice
Example: Union and Intersection Types
Type aliases are especially useful for unions and intersections.
type Status = "active" | "inactive" | "pending";
let currentStatus: Status = "active";
type Address = { city: string; zip: number };
type Contact = { phone: string };
type Profile = Address & Contact;
let profile: Profile = { city: "New York", zip: 10001, phone: "123-456-7890" };
Interfaces
An interface is a TypeScript construct to define the structure of objects. Unlike type aliases, interfaces are primarily designed for object shapes and can be extended.
Syntax
interface InterfaceName {
propertyName: type;
}
Example: Basic Object
interface User {
name: string;
age: number;
isActive: boolean;
}
let user: User = { name: "Bob", age: 25, isActive: true };
console.log(user.age); // Output: 25
Example: Extending Interfaces
Interfaces can extend other interfaces to reuse and combine structures.
interface Address {
city: string;
zip: number;
}
interface Employee extends Address {
name: string;
role: string;
}
let employee: Employee = { name: "John", role: "Manager", city: "Boston", zip: 02115 };
Key Differences Between Type Aliases and Interfaces
Feature | Type Alias | Interface |
---|---|---|
Primary Use | Works with objects, unions, intersections, and primitives | Primarily for object shapes |
Extensibility | Cannot extend after creation | Can extend using extends |
Union and Intersection | Supported | Not supported |
Declaration Merging | Not supported | Supported (allows multiple declarations) |
Classes | Rarely used | Commonly used to define class shapes |
When to Use Type Aliases
1. For Union and Intersection Types
Type aliases are the only way to represent union and intersection types.
type Result = "success" | "failure";
type DetailedError = { message: string } & { code: number };
2. For Complex Combinations
If your type requires a mix of objects, arrays, or primitive types, use a type alias.
type ApiResponse = { status: number; data: any } | { error: string };
3. For Primitive Wrappers
Type aliases are ideal for naming custom types derived from primitives.
type ID = string;
type Price = number;
When to Use Interfaces
1. For Object Shapes
Interfaces are perfect for defining the structure of an object.
interface Car {
make: string;
model: string;
year: number;
}
2. When Extensibility Is Required
Interfaces allow you to extend and compose types.
interface Person {
name: string;
age: number;
}
interface Employee extends Person {
role: string;
}
3. For Classes
Interfaces work seamlessly with classes to define contracts.
interface Shape {
area(): number;
}
class Circle implements Shape {
radius: number;
constructor(radius: number) {
this.radius = radius;
}
area(): number {
return Math.PI * this.radius * this.radius;
}
}
Advanced Features
Declaration Merging with Interfaces
Interfaces support merging declarations, which is useful for adding new properties to existing interfaces.
interface User {
name: string;
}
interface User {
age: number;
}
let user: User = { name: "Alice", age: 25 }; // Merged structure
Generics in Type Aliases and Interfaces
Both type aliases and interfaces support generics.
Example: Type Alias
type Wrapper<T> = { value: T };
let stringWrapper: Wrapper<string> = { value: "Hello" };
Example: Interface
interface Wrapper<T> {
value: T;
}
let numberWrapper: Wrapper<number> = { value: 42 };
Common Mistakes
1. Overusing Type Aliases for Object Shapes
While type aliases work for objects, interfaces are generally preferred for clarity and extensibility.
// Prefer interfaces for object shapes:
interface User { name: string; age: number; }
2. Confusing Union Types with Interfaces
Interfaces cannot represent union types.
// Use type alias for unions:
type Status = "active" | "inactive";
Best Practices
- Use Interfaces for Objects: For defining structured objects, interfaces are clearer and easier to extend.
- Use Type Aliases for Primitives and Unions: When working with unions, intersections, or primitives, type aliases are the way to go.
- Combine Both When Necessary: There’s no harm in mixing both type aliases and interfaces in the same codebase, depending on the use case.
Conclusion
Understanding when to use Type Aliases and Interfaces is key to writing clean and maintainable TypeScript code. While they share similarities, their differences make them uniquely suited for specific scenarios.