TypeScript 7 ๐ท Type Aliases and Interfaces
TypeScript gives you two ways to name a type: type aliases (type) and interfaces (interface). They overlap heavily โ you can describe almost anything with either โ but they’re not interchangeable. Each has features the other lacks, and the choice between them has real consequences for declaration merging, error messages, and how you structure a codebase. Knowing when to use which is one of those small decisions that shapes everything downstream.
Key point: interface declares a named object shape that can be extended, implemented, and merged. type declares an alias for any type expression โ objects, unions, tuples, primitives, functions, you name it. If you’re describing an object, both work. If you’re describing anything else, or composing with unions and intersections, type is the only option. The community has largely settled on a default, but the reasons matter more than the rule.
Type aliases โ type
A type alias gives a name to any type.
type ID = string | number;
type Point = { x: number; y: number };
type Handler = (event: Event) => void;
type Pair = [string, number];
type Status = 'loading' | 'ready' | 'error';
type Maybe<T> = T | null | undefined;
Every one of those is a type alias. ID aliases a union, Point an object, Handler a function, Pair a tuple, Status a literal union, Maybe<T> a generic.
What a type alias can describe:
- Primitives โ
type Name = string - Unions โ
type ID = string | number - Intersections โ
type Entity = Base & Timestamps - Tuples โ
type Point = [number, number] - Functions โ
type Fn = (x: number) => string - Literal unions โ
type Status = 'a' | 'b' - Object shapes โ
type User = { name: string } - Mapped types โ
type Optional<T> = { [K in keyof T]?: T[K] } - Conditional types โ
type IsString<T> = T extends string ? true : false - Generic types โ
type Result<T> = { ok: true; value: T } | { ok: false; error: string }
That breadth is the point. A type alias can name any type expression, no matter how complex.
Using a type alias:
type User = {
id: number;
name: string;
};
const alice: User = { id: 1, name: 'Alice' };
function greet(user: User): string {
return `Hello, ${user.name}`;
}
The syntax is identical to using an interface. The difference is what you can put on the right-hand side.
A type alias is not a new type โ it’s an alias. User and { id: number; name: string } are the same type. You can assign between them freely.
type A = { x: number };
type B = { x: number };
const a: A = { x: 1 };
const b: B = a; // โ
structurally identical
That’s structural typing โ the alias is a name for the shape, not a distinct nominal type.
Why “type alias” and not “type definition”: Because that’s what it is โ a name for something that already exists. The underlying type isn’t new; the name is. That’s different from classes and enums, which create runtime values. A type alias exists only in the type system.
Interfaces โ interface
An interface declares a named object shape.
interface User {
id: number;
name: string;
}
interface Point {
x: number;
y: number;
}
interface Handler {
(event: Event): void;
}
The syntax uses interface Name { ... }. The body lists members โ properties, methods, index signatures, call signatures, construct signatures.
What an interface can describe:
- Object shapes
- Function shapes (with a call signature)
- Constructor shapes (with a construct signature)
- Classes (via
implements) - Index signatures โ
[key: string]: number - Callable objects โ properties plus a call signature
- Extension of other interfaces and classes
- Declaration merging (adding to an existing interface from another file)
What an interface cannot describe:
- Union types
- Intersection types as the whole declaration
- Tuples
- Primitives
- Conditional types
- Mapped types
For any of those, you need type.
Using an interface:
interface User {
id: number;
name: string;
}
const alice: User = { id: 1, name: 'Alice' };
Identical usage to a type alias. The difference is in what you can define.
Why interfaces exist at all: They predate
typein TypeScript’s history, and they map cleanly to how object-oriented programmers think โ a named shape that can be implemented by classes and extended by other interfaces. They also support declaration merging, whichtypedoesn’t. That single feature is why libraries and ambient declarations use interfaces heavily.
The overlap โ when either works
For object shapes, both syntaxes work identically.
// Both describe the same shape
type UserType = {
id: number;
name: string;
};
interface UserInterface {
id: number;
name: string;
}
const a: UserType = { id: 1, name: 'Alice' };
const b: UserInterface = { id: 1, name: 'Alice' };
The two types are structurally identical. You can assign between them.
const c: UserInterface = a; // โ
const d: UserType = b; // โ
Extension also works in both:
// Interface extends
interface Animal {
name: string;
}
interface Dog extends Animal {
breed: string;
}
// Type alias with intersection
type AnimalT = { name: string };
type DogT = AnimalT & { breed: string };
Both produce a type with name and breed. Interface extends and type & are equivalent for object composition.
The practical difference shows up in two places:
- Declaration merging โ interfaces can be merged; type aliases can’t
- Error messages โ interfaces show their name; type aliases sometimes expand inline
And one more: implementing in a class.
interface Serializable {
serialize(): string;
}
class User implements Serializable {
serialize(): string { return JSON.stringify(this); }
}
A class can implement an interface. A class can’t implement a type alias that isn’t an object type โ and even for object-shaped aliases, implements with a union alias fails.
Why the overlap is fine: TypeScript’s team kept both because they serve overlapping but distinct use cases. When they overlap โ describing a plain object โ either works, and consistency matters more than which one. When they don’t overlap โ union, tuple, primitive, conditional โ the choice is forced.
Declaration merging
This is the single feature interfaces have that type aliases don’t. An interface can be declared multiple times, and TypeScript merges the declarations.
interface User {
id: number;
}
interface User {
name: string;
}
// Merged:
// interface User {
// id: number;
// name: string;
// }
const alice: User = { id: 1, name: 'Alice' };
Both id and name are required. TypeScript merges the two declarations into one.
What merges:
- Interfaces with the same name in the same scope
- Namespaces
- Enums (partially)
- A namespace and a class/function/enum with the same name
What doesn’t merge:
- Type aliases โ declaring the same
typename twice is an error - Classes โ duplicate class declarations are an error
Where merging is useful:
Augmenting third-party libraries. A library declares interface Window { ... } and you add a property:
// In your code
declare global {
interface Window {
myApp: {
version: string;
};
}
}
window.myApp.version; // โ
Ambient declarations. When .d.ts files from different sources describe the same interface, they merge.
Plugin architecture. A library exposes an interface that consumers extend with their own fields.
Why merging matters: Libraries can’t know what fields a consumer will add. Declaration merging lets the consumer extend the interface without modifying the library. Without it, every extension would need a subclass or a wrapper type โ and neither composes as cleanly.
Why type aliases don’t merge: A type alias is an alias โ
type A = Xโ sotype A = Ywould be two names for the same identifier, which is a conflict. An interface declares a shape that can grow. The two models are fundamentally different, and merging is a consequence of that.
Extending โ extends vs &
Both interfaces and type aliases support extension, but the mechanism differs.
Interface extends:
interface Animal {
name: string;
}
interface Dog extends Animal {
breed: string;
}
The child inherits the parent’s members. Interface extends accepts:
- Another interface
- A class (rare)
- Multiple parents:
interface Dog extends Animal, Pet - Generics:
interface Box<T> extends Container<T>
Type alias intersection:
type Animal = { name: string };
type Dog = Animal & { breed: string };
The child is an intersection. It accepts:
- Another type alias
- An interface
- Multiple types:
A & B & C - Generics:
type Box<T> = Container<T> & { value: T }
The subtle differences:
Conflict detection. Interfaces catch conflicts at the extends site:
interface A { value: string; }
interface B { value: number; }
interface C extends A, B { } // โ error: property value has conflicting types
Type aliases resolve conflicts via intersection โ the property becomes never:
type A = { value: string };
type B = { value: number };
type C = A & B;
// C.value is string & number โ never
// No error at the declaration, but C is unusable
When you want conflicts to be errors, interface extends is safer. When you’re combining types that might overlap and want them to merge (or you know they don’t conflict), intersection works.
Performance. interface extends produces types TypeScript can cache by name. Deeply nested intersections can slow the compiler. For large codebases, interfaces extend faster.
Both produce the same shape for non-conflicting cases. The choice between them is about error handling and compiler behavior.
Why
interface extendserrors on conflict: An interface declares one shape with a required set of members. If two parents require different types for the same member, there’s no valid shape โ so it’s an error. An intersection declares “both types,” and where they conflict, the intersection is empty for that property โnever. Both are consistent; they’re just different trade-offs.
Interface vs type alias โ full comparison
| Feature | Interface | Type Alias |
|---|---|---|
| Object shape | โ | โ |
| Function shape | โ (call signature) | โ |
| Union type | โ | โ |
| Intersection at top level | โ | โ |
| Tuple | โ | โ |
| Primitive alias | โ | โ |
| Literal union | โ | โ |
| Mapped type | โ | โ |
| Conditional type | โ | โ |
| Template literal type | โ | โ |
| Extends another | โ
(extends) | โ
(&) |
| Implements (by class) | โ | โ ๏ธ object aliases only |
| Declaration merging | โ | โ |
| Generic | โ | โ |
| Index signature | โ | โ |
| Call signature | โ | โ |
| Construct signature | โ | โ |
| Recursive | โ | โ |
| Error conflicts at declaration | โ | โ |
| Cached by name | โ | โ ๏ธ sometimes |
| Duplicate name | โ merges | โ error |
When the answer is obvious:
- Need a union โ
type - Need a tuple โ
type - Need a primitive alias โ
type - Need a conditional or mapped type โ
type - Need declaration merging โ
interface - Need a class to implement โ
interface
When either works โ plain object shapes:
The community’s default is: use interface for object shapes, type for everything else. This is not a law; it’s a convention that most codebases and style guides follow.
Why that default: Interfaces are designed for the object case. They extend cleanly, error on conflicts, cache well, and support merging. Type aliases are the general-purpose tool โ they can do objects but exist to cover the cases interfaces can’t. Using each for what it’s designed for keeps code consistent and compiler-friendly.
Why the convention matters
The TypeScript team’s own guidance and the Google TypeScript Style Guide both recommend preferring interfaces over type aliases for object types. The reasons:
Better error messages. When a type mismatch happens, the interface name appears in the error rather than the expanded shape.
interface User { id: number; name: string }
type UserT = { id: number; name: string };
const u1: User = { id: 'x', name: 'Alice' };
// Error: Type 'string' is not assignable to type 'number'.
// Property 'id' of type 'User'
const u2: UserT = { id: 'x', name: 'Alice' };
// Error: Type 'string' is not assignable to type 'number'.
// Property 'id' of type '{ id: number; name: string }'
The interface version names the type; the alias version expands it. Over long sessions of debugging, that difference adds up.
Faster compilation. Interfaces are cached by name. Type aliases may be re-evaluated in some scenarios. For very large codebases, the difference is measurable.
Declaration merging. Interfaces can be extended by consumers without touching the original. Libraries rely on this.
Extends errors earlier. Conflicts are caught where the extension happens, not where the type is used.
What to do: Use interface for object shapes. Use type for unions, tuples, primitives, functions when you want a named alias, and anything more complex. Both are fine for object shapes, but the convention has concrete benefits.
Why not always use
type: Some codebases standardize ontypefor everything and do fine. It works. But you lose declaration merging, error messages are worse, and extends doesn’t catch conflicts. For a small codebase that never merges, those costs are small. For a large one, they compound. Following the convention costs nothing and avoids the issues.
Advanced uses
Interfaces with call signatures โ callable objects:
interface Logger {
(message: string): void;
level: 'info' | 'warn' | 'error';
}
declare const log: Logger;
log('hello'); // callable
log.level; // has a property
An interface can describe something callable and with properties.
Interfaces with construct signatures:
interface UserConstructor {
new (name: string): { name: string };
}
function createUser(Ctor: UserConstructor, name: string) {
return new Ctor(name);
}
Index signatures:
interface Dictionary {
[key: string]: number;
}
interface WithId {
id: string;
[key: string]: string;
}
Recursive interfaces:
interface TreeNode {
value: number;
children: TreeNode[];
}
Generic interfaces:
interface Box<T> {
value: T;
}
interface Pair<A, B> {
first: A;
second: B;
}
All of these work with type aliases too, except declaration merging. The choice between them here is stylistic.
Why interfaces support call and construct signatures: They model JavaScript’s flexible reality. Functions are objects; constructors are functions; anything can have properties. Interfaces let you describe all of that declaratively. Type aliases can express the same things with different syntax.
A full example
A small domain model that uses both interfaces and type aliases, choosing based on the rule.
// ============================================
// INTERFACES โ object shapes
// ============================================
interface Timestamps {
createdAt: Date;
updatedAt: Date;
}
interface SoftDelete {
deletedAt: Date | null;
}
interface User extends Timestamps, SoftDelete {
id: string;
name: string;
email: string;
role: Role;
}
// ============================================
// TYPE ALIASES โ unions, primitives, functions
// ============================================
type Role = 'admin' | 'user' | 'guest';
type UserId = string;
type UserMap = Record<UserId, User>;
type UserHandler = (user: User) => void;
type Result<T> =
| { ok: true; value: T }
| { ok: false; error: string };
// ============================================
// USING THEM TOGETHER
// ============================================
function findUser(
users: UserMap,
id: UserId
): Result<User> {
const user = users[id];
if (!user) {
return { ok: false, error: `User ${id} not found` };
}
return { ok: true, value: user };
}
function process(users: UserMap, handler: UserHandler): void {
for (const user of Object.values(users)) {
if (user.deletedAt === null) {
handler(user);
}
}
}
// Usage
const alice: User = {
id: 'u-1',
name: 'Alice',
email: 'alice@example.com',
role: 'admin',
createdAt: new Date(),
updatedAt: new Date(),
deletedAt: null
};
const users: UserMap = { [alice.id]: alice };
const result = findUser(users, 'u-1');
if (result.ok) {
console.log('Found:', result.value.name);
} else {
console.log('Error:', result.error);
}
What’s an interface here: Timestamps, SoftDelete, User โ object shapes with extensions.
What’s a type alias here: Role, UserId, UserMap, UserHandler, Result<T> โ a literal union, a primitive alias, a Record utility, a function signature, and a generic discriminated union.
The choice follows the rule: interfaces for object shapes, type aliases for everything else.
Why this combination: Every type does the job it’s best at.
UserextendsTimestampsandSoftDeletevia interface inheritance โ clean and cached.Result<T>is a discriminated union โ impossible with an interface. Each type maps to its purpose, and the codebase is more readable because of it.
Complete Example Session
# ============================================
# PART 1: INTERFACE BASICS
# ============================================
cat > interfaces.ts << 'EOF'
interface User {
id: number;
name: string;
}
const alice: User = { id: 1, name: 'Alice' };
// Declaration merging
interface User {
email?: string;
}
const bob: User = { id: 2, name: 'Bob', email: 'bob@example.com' };
console.log(alice, bob);
EOF
npx tsc --noEmit interfaces.ts
# (no errors)
# ============================================
# PART 2: TYPE ALIAS BASICS
# ============================================
cat > aliases.ts << 'EOF'
type ID = string | number;
type Point = { x: number; y: number };
type Handler = (event: Event) => void;
type Pair = [string, number];
type Status = 'loading' | 'ready' | 'error';
const id: ID = 'abc';
const p: Point = { x: 1, y: 2 };
const s: Status = 'ready';
console.log(id, p, s);
EOF
npx tsc --noEmit aliases.ts
# (no errors)
# ============================================
# PART 3: DECLARATION MERGING
# ============================================
cat > merge.ts << 'EOF'
interface Config {
apiUrl: string;
}
interface Config {
timeout: number;
}
const cfg: Config = {
apiUrl: 'https://api.example.com',
timeout: 5000
};
// The following would fail โ type aliases don't merge
// type T = { a: string };
// type T = { b: number }; // โ duplicate identifier
console.log(cfg);
EOF
npx tsc --noEmit merge.ts
# (no errors)
# ============================================
# PART 4: INTERFACE EXTENDS VS INTERSECTION
# ============================================
cat > extend.ts << 'EOF'
interface Animal {
name: string;
}
interface Dog extends Animal {
breed: string;
}
type AnimalT = { name: string };
type DogT = AnimalT & { breed: string };
const d1: Dog = { name: 'Rex', breed: 'Lab' };
const d2: DogT = { name: 'Rex', breed: 'Lab' };
// Interface catches conflicts
interface A { value: string; }
interface B { value: number; }
// interface C extends A, B { } // โ conflicting types
// Type alias produces never
type AT = { value: string };
type BT = { value: number };
type CT = AT & BT;
// CT.value is never
console.log(d1, d2);
EOF
npx tsc --noEmit extend.ts
# (no errors)
# ============================================
# PART 5: FULL DOMAIN EXAMPLE
# ============================================
cat > domain.ts << 'EOF'
interface Timestamps {
createdAt: Date;
updatedAt: Date;
}
interface User extends Timestamps {
id: string;
name: string;
role: Role;
}
type Role = 'admin' | 'user' | 'guest';
type Result<T> =
| { ok: true; value: T }
| { ok: false; error: string };
function makeUser(name: string): Result<User> {
if (!name) return { ok: false, error: 'Name required' };
return {
ok: true,
value: {
id: 'u-1',
name,
role: 'user',
createdAt: new Date(),
updatedAt: new Date()
}
};
}
const r = makeUser('Alice');
console.log(r.ok ? r.value.name : r.error);
EOF
npx tsc --noEmit domain.ts
# (no errors)
# ============================================
# PART 6: COMPILE AND RUN
# ============================================
npx tsc interfaces.ts aliases.ts merge.ts extend.ts domain.ts
node interfaces.js
# [ { id: 1, name: 'Alice' } { id: 2, name: 'Bob', email: 'bob@example.com' } ]
node aliases.js
# [ abc { x: 1, y: 2 } ready ]
node domain.js
# [ Alice ]
Quick Reference
Syntax
| Form | Purpose |
|---|---|
interface Name { ... } | Declare an object shape |
type Name = ... | Alias any type expression |
What Each Can Describe
| Feature | Interface | Type Alias |
|---|---|---|
| Object shape | โ | โ |
| Union | โ | โ |
| Intersection | โ | โ |
| Tuple | โ | โ |
| Primitive alias | โ | โ |
| Literal union | โ | โ |
| Function signature | โ | โ |
| Mapped type | โ | โ |
| Conditional type | โ | โ |
| Template literal type | โ | โ |
| Index signature | โ | โ |
| Call signature | โ | โ |
| Construct signature | โ | โ |
Extension
| Interface | Type Alias | |
|---|---|---|
| Syntax | extends | & |
| Multiple parents | extends A, B | A & B |
| Conflict detection | โ At declaration | โ Silent โ never |
| Cached by name | โ | โ ๏ธ Sometimes |
Declaration Merging
| Interface | Type Alias | |
|---|---|---|
| Duplicate name | โ Merges | โ Error |
| Use case | Augment libraries | Not possible |
Class Implementation
| Interface | Type Alias | |
|---|---|---|
class X implements Name | โ | โ (object aliases only) |
| Union alias | โ | โ |
When to Use Which
| Use case | Recommendation |
|---|---|
| Object shape | interface |
| Union | type |
| Intersection | type |
| Tuple | type |
| Primitive alias | type |
| Function type | type (or interface with call signature) |
| Literal union | type |
| Declaration merging | interface |
| Class implements | interface |
| Mapped/conditional type | type |
Common Utility Type Aliases
| Alias | Meaning |
|---|---|
type ID = string | number | ID type |
type Status = 'a' | 'b' | Literal union |
type Handler = (x: T) => void | Function type |
type Maybe<T> = T | null | undefined | Nullable |
type Result<T> = { ok: true; value: T } | { ok: false; error: string } | Result type |
Best Practices
โ Do This:
// Use interfaces for object shapes
interface User { id: number; name: string; } // โ
// Use type aliases for unions, tuples, primitives
type Status = 'loading' | 'ready'; // โ
type Pair = [string, number]; // โ
type ID = string | number; // โ
// Use interfaces to extend cleanly
interface Admin extends User { permissions: string[]; } // โ
// Use interfaces for class contracts
interface Serializable { serialize(): string; } // โ
// Use declaration merging to augment
declare global {
interface Window { myApp: App; } // โ
}
// Use generic type aliases for complex patterns
type Result<T> = { ok: true; value: T } | { ok: false; error: string }; // โ
// Prefer interface extends for conflict detection
interface C extends A, B { } // โ
โ Don’t Do This:
// Don't use type alias for object where interface fits
type User = { id: number; name: string }; // โ ๏ธ interface preferred
// Don't try to merge type aliases
type A = { x: 1 };
type A = { y: 2 }; // โ duplicate identifier // โ
// Don't intersect conflicting object types
type C = { x: string } & { x: number }; // โ ๏ธ x is never
// Don't use interface for unions
interface X = 'a' | 'b'; // โ syntax error // โ
// Don't use type alias for tuples where object is clearer
type User = [string, string, number]; // โ ๏ธ use interface
// Don't ignore the convention inconsistently
type A = { x: 1 };
interface B { y: 2 } // โ ๏ธ pick one style
Common Pitfalls
| Pitfall | Problem | Solution |
|---|---|---|
| Trying to merge type aliases | Duplicate identifier error | Use interfaces |
| Conflicting interface extends | Compile error | Resolve conflict |
| Conflicting intersection | Property becomes never | Align types |
Using interface for unions | Syntax error | Use type |
| Expecting type aliases to be nominal | Structural โ they’re aliases | Understand structural typing |
Missing declare global | Augmentation doesn’t apply | Wrap in declare global |
| Naming a class and interface the same | Merging may surprise | Understand class/interface merge |
Using type everywhere | Lose merging, worse errors | Follow convention |
| Extending a class in interface | Confusing side effects | Understand the rules |
Real-World Examples
1. Interface for a user
interface User {
id: number;
name: string;
email: string;
}
2. Type alias for a union
type Status = 'idle' | 'loading' | 'ready' | 'error';
3. Type alias for a tuple
type Point = [x: number, y: number];
4. Type alias for a primitive
type UserId = string;
5. Type alias for a function
type Handler = (event: Event) => void;
6. Interface extension
interface Admin extends User {
permissions: string[];
}
7. Type alias with intersection
type Admin = User & { permissions: string[] };
8. Declaration merging
interface User { id: number; }
interface User { name: string; }
// User has both id and name
9. Augmenting Window
declare global {
interface Window {
analytics: Analytics;
}
}
10. Class implements interface
interface Serializable {
serialize(): string;
}
class User implements Serializable {
serialize(): string { return JSON.stringify(this); }
}
11. Generic interface
interface Box<T> {
value: T;
}
12. Generic type alias
type Maybe<T> = T | null;
13. Discriminated union via type alias
type Result<T> =
| { ok: true; value: T }
| { ok: false; error: string };
14. Recursive interface
interface TreeNode {
value: number;
children: TreeNode[];
}
15. Index signature
interface Dictionary {
[key: string]: number;
}
16. Callable interface
interface Logger {
(message: string): void;
level: 'info' | 'error';
}
17. Construct signature
interface Constructor {
new (name: string): object;
}
18. Interface extending a class
class Point {
x = 0;
y = 0;
}
interface Point3D extends Point {
z: number;
}
19. Literal union type alias
type Direction = 'north' | 'south' | 'east' | 'west';
20. Combined pattern
interface Timestamps {
createdAt: Date;
updatedAt: Date;
}
interface Entity extends Timestamps {
id: string;
}
type Result<T> =
| { ok: true; value: T }
| { ok: false; error: string };
Visual: Interface vs Type Alias
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ interface โ
โ โ
โ interface User { โ
โ id: number; โ
โ name: string; โ
โ } โ
โ โ
โ โข Object shapes โ
โ โข Extends other interfaces/classes โ
โ โข Implements in classes โ
โ โข Declaration merging โ
โ โข Cached by name โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ type alias โ
โ โ
โ type User = { โ
โ id: number; โ
โ name: string; โ
โ }; โ
โ โ
โ โข Any type expression โ
โ โข Unions, tuples, primitives โ
โ โข Mapped, conditional types โ
โ โข No merging โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Visual: The Venn Diagram
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ โ
โ โโโโโโโโโโโโโโโโโโโโ โ
โ โ โ โ
โ โ Both can do: โ โ
โ โ โ โ
โ โ โข Object shape โ โ
โ โ โข Function sig โ โ
โ โ โข Generics โ โ
โ โ โข Extend โ โ
โ โ โข Recursive โ โ
โ โ โ โ
โ โโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ interface only: type only: โ
โ โ
โ โข Merging โข Unions โ
โ โข Extends A, B โข Intersections โ
โ โข Class implements โข Tuples โ
โ โข Cached name โข Primitives โ
โ โข Literal unions โ
โ โข Mapped types โ
โ โข Conditional types โ
โ โข Template literals โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Visual: Declaration Merging
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ File A: โ
โ โ
โ interface User { โ
โ id: number; โ
โ } โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ File B: โ
โ โ
โ interface User { โ
โ name: string; โ
โ } โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โ merged by the compiler
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Effective User: โ
โ โ
โ interface User { โ
โ id: number; โ
โ name: string; โ
โ } โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Visual: Extends vs Intersection
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ interface extends โ
โ โ
โ interface A { x: string; } โ
โ interface B { x: number; } โ
โ interface C extends A, B { } โ
โ โ
โ โ Error at declaration โ
โ (conflicting types for x) โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ type & intersection โ
โ โ
โ type A = { x: string }; โ
โ type B = { x: number }; โ
โ type C = A & B; โ
โ โ
โ โ ๏ธ No error at declaration โ
โ C.x becomes never โ unusable โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Visual: When to Use Which
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Plain object? โ
โ โ โ
โ โโโ YES โโโบ interface โ
โ โ โ
โ โโโ NO โ
โ โ โ
โ โโโ Union? โโโบ type โ
โ โโโ Tuple? โโโบ type โ
โ โโโ Primitive? โโโบ type โ
โ โโโ Mapped/conditional? โโโบ type โ
โ โโโ Function? โโโบ type โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Visual: Error Message Quality
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ With interface: โโ โ
โ interface User { id: number } โ
โ const u: User = { id: 'x' }; โ
โ โ
โ Error: Type 'string' is not assignable โ
โ to type 'number'. โ
โ Property 'id' of type 'User'. โ
โ โ โ
โ name shown โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ With type alias: โ
โ โ
โ type User = { id: number }; โ
โ const u: User = { id: 'x' }; โ
โ โ
โ Error: Type 'string' is not assignable โ
โ to type 'number'. โ
โ Property 'id' of type '{ id: number }'. โ
โ โ โ
โ expanded โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Summary
| Concept | Interface | Type Alias |
|---|---|---|
| Object shapes | โ | โ |
| Unions | โ | โ |
| Tuples | โ | โ |
| Primitives | โ | โ |
| Conditional types | โ | โ |
| Mapped types | โ | โ |
| Function signatures | โ | โ |
| Declaration merging | โ | โ |
| Class implements | โ | โ ๏ธ object aliases only |
| Cached by name | โ | โ ๏ธ sometimes |
| Conflict detection on extend | โ | โ silent never |
Key takeaways:
interfacedeclares a named object shape;typealiases any type expression- Both can describe object shapes โ the overlap is real
- Only
typecan describe unions, intersections, tuples, primitives, mapped types, conditional types, and template literal types - Only
interfacesupports declaration merging โ multiple declarations with the same name merge interface extendscatches conflicts at the declaration;type &silently producesnever- Classes implement interfaces directly โ
implements Name - Error messages are better with interfaces โ the name appears instead of the expanded shape
- Compilation is faster with interfaces โ they’re cached by name
- The community convention:
interfacefor object shapes,typefor everything else - Neither is “better” โ each is designed for a specific set of use cases
- Use
typewhen you need a union, tuple, primitive alias, or complex type - Use
interfacewhen you need merging, better errors, or a class contract
Remember: Both name types. Interfaces are specialized for object shapes โ they extend cleanly, merge across files, and produce good error messages. Type aliases are general-purpose โ they name anything, including unions and tuples that interfaces can’t express. The convention โ interface for objects, type for the rest โ isn’t arbitrary; it reflects how the two features are designed. Follow it and both features do their best work.
Stop using slow, ad-bloated tool sites! ๐คฎ
๐ Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
โ
Finance (Mortgage, Interest, Inflation)
โ
Tech (Base64, JSON, Dev Suite, IP)
โ
Health (BMI, BMR, TDEE)
โ
Productivity (Timer, Workspace, QR)
โก๏ธ Fast & Private
๐ No data leaves your device
๐ 100% Free
๐ Use it now: https://tools.kandz.me
๐ Bookmark itโyouโll need it later!