Advanced TypeScript Type System Explained
TypeScript’s type system is one of its most powerful features. Understanding advanced type concepts can significantly improve code quality and developer productivity. Let’s dive deep into the advanced features.
1. Generic Types
Generics allow you to write reusable code that works with multiple types:
// Basic Generic Function
function identity<T>(arg: T): T {
return arg;
}
// Using with different types
const numResult = identity<number>(42);
const strResult = identity<string>("hello");
// Generic Constraints
interface HasName {
name: string;
}
function printName<T extends HasName>(obj: T): void {
console.log(obj.name);
}
// Generic Classes
class Container<T> {
private value: T;
constructor(value: T) {
this.value = value;
}
getValue(): T {
return this.value;
}
}
2. Conditional Types
Conditional types enable type selection based on conditions:
// Basic Conditional Type
type IsString<T> = T extends string ? true : false;
type A = IsString<"hello">; // true
type B = IsString<123>; // false
// Practical Example: Flatten Array Type
type Flatten<T> = T extends Array<infer U> ? U : T;
type Str = Flatten<string[]>; // string
type Num = Flatten<number>; // number
// Conditional Types with Generics
type IsArray<T> = T extends any[] ? 'array' : 'not-array';
type Validation<T> = T extends undefined ? 'invalid' : 'valid';
3. Mapped Types
Mapped types create new types by transforming properties:
// Basic Mapped Type - Make all properties readonly
type Readonly<T> = {
readonly [K in keyof T]: T[K];
};
interface User {
id: number;
name: string;
email: string;
}
type ReadonlyUser = Readonly<User>;
// Result: { readonly id: number; readonly name: string; readonly email: string; }
// Property Modifier Mapping
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
type GettersUser = Getters<User>;
// Result: { getId: () => number; getName: () => string; getEmail: () => string; }
// Conditional Mapped Type
type Getters2<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: T[K] extends object
? () => string
: () => T[K];
};
4. Template Literal Types
Template literal types allow type-safe string manipulation:
// Basic Template Literal Type
type EventName = `on${Capitalize<'click'>}`; // "onClick"
// String Union Types with Template Literals
type Color = 'red' | 'blue' | 'green';
type ColorString = `color-${Color}`;
// Results in: 'color-red' | 'color-blue' | 'color-green'
// Creating API Endpoints
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
type ApiEndpoint = `/${HttpMethod}/${'users' | 'posts' | 'comments'}`;
// Results in: '/GET/users' | '/GET/posts' | '/POST/users' | etc.
// Extracting String Parts
type EmailAddress = string & { readonly brand: 'email' };
type ValidateEmail<T> = T extends `${infer Name}@${infer Domain}`
? T
: never;
// Practical Example: Validating Email Format
const email = 'user@example.com' as const;
type ExtractEmail = ValidateEmail<typeof email>; // "user@example.com"
5. Advanced Union and Intersection Types
// Discriminated Unions
type Result<T> =
| { success: true; data: T }
| { success: false; error: string };
function handleResult<T>(result: Result<T>) {
if (result.success) {
console.log(result.data); // TypeScript knows data exists
} else {
console.log(result.error); // TypeScript knows error exists
}
}
// Intersection Types for Composition
interface Animal {
name: string;
age: number;
}
interface Person {
name: string;
email: string;
}
type PersonAnimal = Animal & Person;
// Results in: { name: string; age: number; email: string; }
6. Type Guards and Predicates
Type guards narrow types within conditional blocks:
// Type Guard Function
function isString(value: unknown): value is string {
return typeof value === 'string';
}
function process(value: string | number) {
if (isString(value)) {
// value is string here
console.log(value.toUpperCase());
} else {
// value is number here
console.log(value.toFixed(2));
}
}
// Advanced Type Guard with Generics
function isInstanceOf<T>(
value: unknown,
constructor: new (...args: any[]) => T
): value is T {
return value instanceof constructor;
}
// Custom Type Guard
interface Admin {
role: 'admin';
permissions: string[];
}
interface User {
role: 'user';
profile: any;
}
type UserType = Admin | User;
function isAdmin(user: UserType): user is Admin {
return user.role === 'admin';
}
7. Utility Types Deep Dive
// Record Type - Create object with specific keys
type HttpStatusCodeMap = Record<200 | 404 | 500, string>;
// { 200: string; 404: string; 500: string; }
// Pick Type - Select specific properties
interface Product {
id: string;
name: string;
price: number;
description: string;
}
type ProductPreview = Pick<Product, 'id' | 'name' | 'price'>;
// { id: string; name: string; price: number; }
// Omit Type - Exclude specific properties
type ProductWithoutDescription = Omit<Product, 'description'>;
// { id: string; name: string; price: number; }
// Partial Type - Make all properties optional
type PartialProduct = Partial<Product>;
// { id?: string; name?: string; price?: number; description?: string; }
// Required Type - Make all properties required
type FullProduct = Required<PartialProduct>;
// { id: string; name: string; price: number; description: string; }
// Readonly Type - Make all properties readonly
type ReadonlyProduct = Readonly<Product>;
// { readonly id: string; readonly name: string; ... }
8. Advanced Patterns and Techniques
Builder Pattern with Types
interface QueryBuilder {
select(...fields: string[]): QueryBuilder;
where(condition: string): QueryBuilder;
orderBy(field: string, direction: 'ASC' | 'DESC'): QueryBuilder;
build(): string;
}
class SqlBuilder implements QueryBuilder {
private fields: string[] = [];
private conditions: string[] = [];
private ordering: string = '';
select(...fields: string[]): QueryBuilder {
this.fields.push(...fields);
return this;
}
where(condition: string): QueryBuilder {
this.conditions.push(condition);
return this;
}
orderBy(field: string, direction: 'ASC' | 'DESC'): QueryBuilder {
this.ordering = `${field} ${direction}`;
return this;
}
build(): string {
let query = `SELECT ${this.fields.join(', ')} FROM users`;
if (this.conditions.length > 0) {
query += ` WHERE ${this.conditions.join(' AND ')}`;
}
if (this.ordering) {
query += ` ORDER BY ${this.ordering}`;
}
return query;
}
}
// Usage
const query = new SqlBuilder()
.select('id', 'name', 'email')
.where('age > 18')
.orderBy('name', 'ASC')
.build();
Plugin Architecture with Types
interface Plugin {
name: string;
version: string;
install(app: any): void;
}
type PluginRegistry = Map<string, Plugin>;
class PluginManager {
private plugins: PluginRegistry = new Map();
register<T extends Plugin>(plugin: T): void {
this.plugins.set(plugin.name, plugin);
}
getPlugin<T extends Plugin>(name: string): T | undefined {
return this.plugins.get(name) as T | undefined;
}
}
9. Performance Considerations
// Avoid Deep Recursion in Types
// ❌ BAD: Can cause compilation issues
type DeepAccess<T> = T extends object ? DeepAccess<T[keyof T]> : T;
// ✅ GOOD: Limited depth
type DeepAccessLimited<T, Depth extends number = 3> = Depth extends 0
? T
: T extends object
? DeepAccessLimited<T[keyof T], [-1, 0, 1, 2][Depth]>
: T;
// ✅ GOOD: Use distributive conditional types efficiently
type Flatten<T> = T extends Array<infer U> ? Flatten<U> : T;
// ✅ GOOD: Cache complex types
type CachedType = ReturnType<typeof complexFunction>;
10. Real-World Applications
Type-Safe API Responses
type ApiResponse<T, E = Error> =
| { status: 'success'; data: T }
| { status: 'error'; error: E };
async function fetchUser(id: string): Promise<ApiResponse<User>> {
try {
const response = await fetch(`/api/users/${id}`);
const data: User = await response.json();
return { status: 'success', data };
} catch (error) {
return { status: 'error', error: error as Error };
}
}
Conclusion
Mastering TypeScript’s advanced type system unlocks powerful capabilities:
- Type Safety: Catch errors at compile-time, not runtime
- Better Documentation: Types serve as documentation
- Improved Productivity: IDE support and autocomplete
- Maintainability: Refactoring becomes safer and easier
As you continue working with TypeScript, experiment with these advanced features and discover how they can improve your code quality and development workflow.