Back to Blog
TypeScriptInterfacesTypesBest Practices

TypeScript Interface vs Type

Understand the differences between interfaces and types in TypeScript. When to use each and best practices.

B
Bootspring Team
Engineering
October 20, 2020
7 min read

Understanding when to use interfaces versus types is essential for TypeScript development. Here's a comprehensive comparison.

Basic Syntax#

1// Interface 2interface User { 3 id: number; 4 name: string; 5 email: string; 6} 7 8// Type alias 9type UserType = { 10 id: number; 11 name: string; 12 email: string; 13}; 14 15// Both work the same for object types 16const user1: User = { id: 1, name: 'Alice', email: 'alice@example.com' }; 17const user2: UserType = { id: 2, name: 'Bob', email: 'bob@example.com' };

Declaration Merging#

1// Interfaces can be merged 2interface Config { 3 apiUrl: string; 4} 5 6interface Config { 7 timeout: number; 8} 9 10// Results in: 11// interface Config { 12// apiUrl: string; 13// timeout: number; 14// } 15 16const config: Config = { 17 apiUrl: 'https://api.example.com', 18 timeout: 5000, 19}; 20 21// Types cannot be merged 22type ConfigType = { 23 apiUrl: string; 24}; 25 26// Error: Duplicate identifier 'ConfigType' 27// type ConfigType = { 28// timeout: number; 29// }; 30 31// This is useful for extending third-party types 32declare module 'express' { 33 interface Request { 34 user?: User; 35 } 36}

Extension and Intersection#

1// Interface extends 2interface Animal { 3 name: string; 4} 5 6interface Dog extends Animal { 7 breed: string; 8} 9 10// Multiple inheritance 11interface Pet extends Animal { 12 owner: string; 13} 14 15interface PetDog extends Dog, Pet { 16 trained: boolean; 17} 18 19// Type intersection 20type AnimalType = { 21 name: string; 22}; 23 24type DogType = AnimalType & { 25 breed: string; 26}; 27 28// Interface extends type 29interface Cat extends AnimalType { 30 meows: boolean; 31} 32 33// Type extends interface 34type Bird = Animal & { 35 canFly: boolean; 36};

Unique Type Features#

1// Union types (only with type) 2type Status = 'pending' | 'active' | 'completed'; 3type ID = string | number; 4 5// Cannot do this with interface 6// interface Status = 'pending' | 'active' // Error 7 8// Mapped types 9type Readonly<T> = { 10 readonly [P in keyof T]: T[P]; 11}; 12 13type Optional<T> = { 14 [P in keyof T]?: T[P]; 15}; 16 17// Conditional types 18type IsString<T> = T extends string ? true : false; 19 20type StringOrNumber<T> = T extends string 21 ? string 22 : T extends number 23 ? number 24 : never; 25 26// Template literal types 27type EventName = 'click' | 'focus' | 'blur'; 28type EventHandler = `on${Capitalize<EventName>}`; 29// 'onClick' | 'onFocus' | 'onBlur' 30 31// Tuple types 32type Point = [number, number]; 33type RGB = [number, number, number]; 34 35// With labels (TypeScript 4.0+) 36type Point3D = [x: number, y: number, z: number];

Unique Interface Features#

1// Implements (classes) 2interface Serializable { 3 serialize(): string; 4} 5 6interface Deserializable { 7 deserialize(data: string): void; 8} 9 10class Document implements Serializable, Deserializable { 11 content: string = ''; 12 13 serialize(): string { 14 return JSON.stringify({ content: this.content }); 15 } 16 17 deserialize(data: string): void { 18 const parsed = JSON.parse(data); 19 this.content = parsed.content; 20 } 21} 22 23// Call signatures 24interface Formatter { 25 (value: string): string; 26 locale: string; 27} 28 29const formatter: Formatter = Object.assign( 30 (value: string) => value.toUpperCase(), 31 { locale: 'en-US' } 32); 33 34// Construct signatures 35interface DateConstructor { 36 new (value: number): Date; 37 new (value: string): Date; 38} 39 40// Index signatures 41interface StringMap { 42 [key: string]: string; 43} 44 45interface NumberMap { 46 [key: number]: string; 47}

Performance Considerations#

1// Interfaces are generally faster for: 2// - Error messages (cleaner type names) 3// - Type checking (cached by name) 4 5// Complex intersections can be slow 6type Complex = A & B & C & D & E & F & G; 7 8// Prefer interface extension 9interface Better extends A, B, C, D {} 10 11// For large codebases, interfaces can provide 12// better performance in the compiler

Practical Guidelines#

1// Use INTERFACE for: 2 3// 1. Object shapes (most common case) 4interface User { 5 id: number; 6 name: string; 7} 8 9// 2. Class contracts 10interface Repository<T> { 11 find(id: string): Promise<T>; 12 save(item: T): Promise<void>; 13} 14 15class UserRepository implements Repository<User> { 16 async find(id: string): Promise<User> { 17 // implementation 18 } 19 async save(user: User): Promise<void> { 20 // implementation 21 } 22} 23 24// 3. Extensible APIs 25interface PluginOptions { 26 name: string; 27} 28 29// Third parties can extend 30declare module 'my-plugin' { 31 interface PluginOptions { 32 customOption: boolean; 33 } 34} 35 36// Use TYPE for: 37 38// 1. Union types 39type Result<T> = T | Error; 40type Theme = 'light' | 'dark' | 'system'; 41 42// 2. Function types 43type Handler = (event: Event) => void; 44type AsyncHandler = (event: Event) => Promise<void>; 45 46// 3. Computed types 47type Keys = keyof User; 48type UserValues = User[keyof User]; 49 50// 4. Mapped types 51type Partial<T> = { [P in keyof T]?: T[P] }; 52type Readonly<T> = { readonly [P in keyof T]: T[P] }; 53 54// 5. Tuples 55type Coordinates = [number, number]; 56type Response = [data: User[], total: number]; 57 58// 6. Complex type expressions 59type DeepPartial<T> = T extends object 60 ? { [P in keyof T]?: DeepPartial<T[P]> } 61 : T;

Common Patterns#

1// React component props - either works 2interface ButtonProps { 3 label: string; 4 onClick: () => void; 5} 6 7type ButtonPropsType = { 8 label: string; 9 onClick: () => void; 10}; 11 12// API response types - type for unions 13type ApiResponse<T> = 14 | { status: 'success'; data: T } 15 | { status: 'error'; message: string }; 16 17// Redux actions - type for discriminated unions 18type Action = 19 | { type: 'INCREMENT'; payload: number } 20 | { type: 'DECREMENT'; payload: number } 21 | { type: 'RESET' }; 22 23// State machines 24type State = 25 | { status: 'idle' } 26 | { status: 'loading' } 27 | { status: 'success'; data: User } 28 | { status: 'error'; error: Error }; 29 30// Generic utilities - type for flexibility 31type Nullable<T> = T | null; 32type Maybe<T> = T | null | undefined; 33type ValueOf<T> = T[keyof T];

Combining Both#

1// Interface for structure, type for unions 2interface User { 3 id: number; 4 name: string; 5} 6 7interface Admin extends User { 8 permissions: string[]; 9} 10 11interface Guest { 12 sessionId: string; 13} 14 15type AnyUser = User | Admin | Guest; 16 17// Function overloads with interface 18interface Calculator { 19 add(a: number, b: number): number; 20 add(a: string, b: string): string; 21} 22 23const calc: Calculator = { 24 add(a: any, b: any) { 25 return a + b; 26 }, 27}; 28 29// Props pattern 30interface BaseProps { 31 className?: string; 32 style?: React.CSSProperties; 33} 34 35type ComponentProps = BaseProps & { 36 variant: 'primary' | 'secondary'; 37 size: 'small' | 'medium' | 'large'; 38};

Migration Considerations#

1// If you need to migrate from one to another: 2 3// Interface to type 4interface OldInterface { 5 prop: string; 6} 7 8type NewType = OldInterface; // Just alias 9 10// Type to interface (if it's an object type) 11type OldType = { 12 prop: string; 13}; 14 15interface NewInterface extends OldType {} 16 17// Note: Not all types can become interfaces 18type Union = 'a' | 'b'; // Cannot be interface 19type Tuple = [string, number]; // Cannot be interface

Best Practices#

Default Choice: ✓ Use interface for object types ✓ Use type for unions, tuples, mapped types ✓ Be consistent within a codebase ✓ Document team conventions Interface When: ✓ Defining object shapes ✓ Creating class contracts ✓ Building extensible APIs ✓ You need declaration merging Type When: ✓ Creating union types ✓ Creating tuple types ✓ Creating mapped/conditional types ✓ You need type computations Avoid: ✗ Mixing styles inconsistently ✗ Over-engineering type definitions ✗ Complex deeply nested types ✗ Circular type references

Conclusion#

Both interfaces and types are powerful TypeScript features. Use interfaces for object shapes and class contracts where declaration merging might be useful. Use types for unions, tuples, and complex type computations. In practice, the choice often comes down to team conventions and specific use cases.

Share this article

Help spread the word about Bootspring