As someone who’s spent countless hours developing with TypeScript, I’ve come to appreciate just how powerful and flexible its type system truly is. Interfaces and type aliases are essential tools that help me write clear, maintainable, and bug-resistant code. In fact, once you get used to describing your data precisely with TypeScript, working without these features feels like coding in the dark.
In this chapter, we’ll explore the fundamental differences between interfaces and type aliases, discover how structural typing makes TypeScript incredibly versatile, and understand why type compatibility is critical in everyday development.

1. Interfaces: Defining Clear Contracts
Interfaces allow you to explicitly outline the shape of objects and classes, setting clear expectations for your code.
Basic Example:
interface User { id: number; name: string; isActive: boolean; } const user1: User = { id: 1, name: "Ada", isActive: true, };
Optional Properties and Readonly
interface UserProfile { readonly id: number; name: string; email?: string; // optional }
Extending Interfaces
Interfaces can extend other interfaces, making your types reusable and flexible.
interface Person { name: string; } interface Employee extends Person { employeeId: number; } const staff: Employee = { name: "Grace", employeeId: 123 };
Describing Function Types
interface Greeter { (name: string): string; } const greet: Greeter = (name) => `Hello, ${name}`;
2. Type Aliases: More Than Just Objects
Type aliases can represent a wide variety of types—including primitives, unions, tuples, intersections, and utilities—offering unmatched flexibility in your codebase.
Basic Example:
type UserID = number | string; let id1: UserID = 42; let id2: UserID = "user_42";
Type Aliases for Objects
type Point = { x: number; y: number }; const pt: Point = { x: 1, y: 2 };
Union and Intersection Types
type Success = { status: "success"; data: string }; type Failure = { status: "error"; error: string }; type Result = Success | Failure; type Named = { name: string }; type Timestamped = { timestamp: Date }; type NamedEvent = Named & Timestamped;
3. Interface vs. Type Alias: Key Differences
Feature | Interface | Type Alias |
---|---|---|
Objects/classes | Yes | Yes |
Primitives/Unions | No | Yes |
Extends/implements | Yes | Limited |
Declaration merging | Yes | No |
- Interfaces: Ideal for outlining object shapes and class implementations.
- Type Aliases: Excel in defining unions, intersections, primitives, and more complex type compositions.
Note: While interfaces and type aliases can often serve similar purposes for object definitions, interfaces are generally preferred unless the flexibility of type aliases is specifically required.
4. Structural Typing: TypeScript’s Secret
TypeScript employs structural typing (also known as “duck typing”). This means two types are considered compatible if their structures match, regardless of their original declarations.
Example:
interface Pet { name: string } type Animal = { name: string } const dog: Pet = { name: "Rex" }; const cat: Animal = dog; // No error!
Here, Pet
and Animal
are compatible because their shapes match.
5. Type Compatibility in Action
In TypeScript, compatibility is all about matching structure, not type names, simplifying and streamlining your code interactions.
interface A { x: number } interface B { x: number; y: number } const b: B = { x: 5, y: 10 }; const a: A = b; // This is fine; B has at least the properties of A
But not the other way around:
const a2: A = { x: 5 }; const b2: B = a2; // Error: missing 'y'
6. Practical Examples
Combining Types with Intersection
type WithId = { id: number }; type WithTimestamps = { createdAt: Date; updatedAt: Date }; type Entity = WithId & WithTimestamps;
Defining API Responses
type ApiResponse = { status: number; body: any };
Extending 3rd-party Types
interface Window { myCustomProperty?: string; }
Quick Recap
- Interfaces: Clearly define the structure and contracts for objects and classes.
- Type Aliases: Offer powerful flexibility for complex type definitions.
- Structural Typing: Emphasizes matching shapes over explicit type names—if it looks like a duck, it’s a duck!
- Compatibility: TypeScript checks are based on structure, simplifying type management.
Next up, we’ll tackle Mastering Functions, Generics, and Elegant Code Reuse, enhancing your ability to write sophisticated, reusable TypeScript code.