Interfaces vs Type Aliases
Two ways to shape data — pick the right tool
In Stage 2, you learned Type Aliases — naming a shape with the type keyword. You could write type Point = { x: number; y: number } and reuse it everywhere. Now meet its sibling: the interface.
Interfaces are another way to name object shapes in TypeScript. Both describe the shape of data, but they have different superpowers and different purposes. Think of the Nizam's Royal Architect who had two ways to define a palace.
A Type Alias is like a quick sketch on a napkin — "Bhai, the palace should have 4 gates and a garden." It is flexible, works for anything (unions, primitives, objects), but once drawn, you cannot modify the same sketch. You would need to create a brand new one from scratch.
An Interface is the official Royal Blueprints — "Palace Blueprint v1." It is specifically designed for object shapes, and here is the real magic: if the Nizam wants to add a new wing later, he simply declares "Palace Blueprint gets a pool" and the blueprint AUTOMATICALLY merges the new wing into itself! This feature is called declaration merging, and it is unique to interfaces.
Interfaces are the formal, extensible blueprints of the TypeScript Nizam's palace — purpose-built for describing and extending object shapes, making them the first choice for library authors and large-scale APIs that need to grow over time.
The interface keyword defines an object shape. The syntax is straightforward — declare the name and list the properties inside curly braces:
interface User {
name: string;
age: number;
}
Compare this with the type alias equivalent:
type User = {
name: string;
age: number;
}
Both work identically for this basic case! The key visual difference: interfaces do not use an equals sign. This is a very common beginner mistake — writing interface User = { ... } will throw a syntax error. It must be interface User { ... } directly.
Here is a more flavorful example:
interface Biryani {
type: string;
spice: number;
hasSaffron: boolean;
}
Functions can use interfaces as parameter types:
function serve(item: Biryani): void {
console.log(item.type);
}
Optional properties use the ? marker, just like type aliases:
interface User {
name: string;
email?: string;
}
Readonly properties can only be set once, at creation time:
interface Product {
readonly id: number;
name: string;
}
Method signatures can be written in two styles:
interface Greeter {
greet(name: string): string;
}
// Alternative shorthand syntax
interface Greeter2 {
greet: (name: string) => string;
}
Both method styles are valid and equivalent. The first is more common in interfaces, the second mirrors how you would write a type alias. Choose whichever your team prefers!
Let us compare interfaces and type aliases side by side across four critical dimensions.
1. Extending: Interfaces use the extends keyword to inherit from another interface:
interface Admin extends User {
role: string;
}
Type aliases use intersection types with the & operator:
type Admin = User & {
role: string;
}
Both achieve the same result, but the extends syntax is often considered cleaner and more readable for object shapes, especially when extending multiple interfaces at once.
2. Declaration Merging: This is the big one. Two interfaces with the same name automatically merge their properties:
interface Box {
size: number;
}
interface Box {
color: string;
}
// Result: Box has BOTH
// { size: number; color: string; }
Type aliases cannot be re-declared with the same name — it is a duplicate identifier error:
type Box = { size: number };
type Box = { color: string };
// Error: Duplicate ident 'Box'
This merging behavior is a powerful feature for library authors who want to allow users to extend types, but it can also cause accidental bugs if you are not careful with naming.
3. What They Can Describe: Interfaces only describe object shapes (and functions or classes). Type aliases can describe anything:
// Primitives - only type
type ID = string;
// Unions - only type
type Status = "open" | "closed";
// Tuples - only type
type Pair = [string, number];
Interfaces cannot directly express unions, primitives, or tuples. They are strictly for object-shaped data.
4. Error Messages: Interface error messages reference the interface name, which can be more readable for deeply nested types. Type alias errors sometimes expand the full shape inline, making them harder to read in complex scenarios.
Here are the most common traps when working with interfaces and type aliases, along with clear guidance on when to use which.
Trap 1: Union Types with Interface. You can have a union as a property value inside an interface:
interface Thing {
kind: "a" | "b";
}
But you cannot make the interface itself a union. This is invalid:
// ERROR - cannot be a union
interface Thing = string | number;
// Correct - use type instead
type Thing = string | number;
For union types, you must use a type alias. Interfaces simply cannot express this pattern.
Trap 2: Accidental Merging. If you declare two interfaces with the same name in the same file, they merge silently. This can be a feature or a bug:
interface Config {
host: string;
}
interface Config {
port: number;
}
// Config now has BOTH host & port!
// Accidental? TypeScript won't warn
If you accidentally name two unrelated interfaces the same thing, TypeScript will not warn you — they will combine silently. This is especially dangerous in large codebases with many developers.
Trap 3: Implementing Unions. A class can implement an interface, but it cannot implement a union type alias:
type A = { x: number } | { y: string };
class Foo implements A {}
// Error: class can only implement
// object type or intersection
Classes can only implement object-shaped types. If you need a class to satisfy a union, you need a different design pattern entirely.
When to Use Which:
- Use
interfacefor object shapes that might be extended or merged, especially for library APIs and public type definitions. - Use
typefor unions, intersections, primitives, tuples, and when you want to prevent accidental merging. - The TypeScript team recommends
interfaceas the default for object shapes andtypefor everything else. - However, many teams simply use
typeeverywhere for consistency. Both are valid — pick one convention and stick with it across your project.
Let us recap everything with a clean cheatsheet comparing interfaces and type aliases.
Declaration Syntax:
// Interface - no equals sign
interface User {
name: string;
age: number;
}
// Type Alias - uses equals sign
type User = {
name: string;
age: number;
}
Extending / Inheriting:
// Interface uses 'extends'
interface Admin extends User {
role: string;
}
// Type uses intersection '&'
type Admin = User & {
role: string;
}
Declaration Merging:
// Interface - merges automatically
interface Box { size: number; }
interface Box { color: string; }
// Result: { size: number; color: string }
// Type - duplicate error
type Box = { size: number };
type Box = { color: string };
// ERROR: Duplicate identifier 'Box'
Cheatsheet Summary:
- Interface:
interface X { ... }, no=, supportsextends, supports declaration merging, object shapes only, cleaner for extension and inheritance. - Type:
type X = ..., uses=, uses&for extension, no merging, works for everything (unions, primitives, tuples, intersections, mapped types).
Key Rules to Remember:
- Both describe object shapes identically — pick whichever feels right for simple cases.
- Interface = the Nizam's official blueprint — mergeable, extendable, formal.
- Type = your flexible sketch — handles everything, no accidental merging.
- You cannot go wrong with either for basic object shapes.
- Use
typefor unions, intersections, primitives, and tuples. - Many teams pick one convention and stick with it across the entire codebase.
The golden rule: "Interface is the Nizam's official blueprint — mergeable and extendable. Type is your flexible sketch — handles everything. Pick the right tool for the job, bhai!"
Key Takeaways
- Interfaces declare object shapes using `interface Name { ... }` — no equals sign
- Declaration merging: same-name interfaces automatically combine their properties
- Type aliases can express unions, primitives, and tuples — interfaces cannot
- Use `extends` with interfaces, `&` with type aliases for extension
- Prefer interface for extensible object shapes, type for everything else
Want to track your progress?
Log in to save your place and pick up where you left off.
Progress track karna chahte ho?
Login karo apni progress save karne ke liye aur jahan chhoda tha wahan se shuru karo.
Login