TypeScript Advanced Types
TypeScript Advanced Types Generics // Generic functions function identity<T>(arg: T): T { return arg; } function first<T>(arr: T[]): T | undefined { return arr[…
TypeScript Advanced Types
Generics
// Generic functions
function identity<T>(arg: T): T { return arg; }
function first<T>(arr: T[]): T | undefined { return arr[0]; }
// Constrained generics
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
getProperty({ name: 'Alice', age: 30 }, 'name'); // OK
getProperty({ name: 'Alice' }, 'foo'); // Error
// Multiple type params
function zip<A, B>(a: A[], b: B[]): [A, B][] {
return a.map((item, i) => [item, b[i]]);
}
// Generic classes
class Repository<T extends { id: string }> {
private items: T[] = [];
findById(id: string): T | undefined {
return this.items.find(i => i.id === id);
}
save(item: T): void { this.items.push(item); }
}
// Generic interfaces
interface ApiResponse<T> {
data: T;
status: number;
message: string;
}
type UserResponse = ApiResponse<User>;
type ListResponse<T> = ApiResponse<{ items: T[]; total: number }>;
// Default generic params
interface State<T = unknown> { value: T; loading: boolean; }Utility Types
interface User {
id: string;
name: string;
email: string;
age?: number;
role: 'admin' | 'user';
}
// Built-in utility types
Partial<User> // all props optional
Required<User> // all props required
Readonly<User> // all props readonly
Pick<User, 'id'|'name'> // subset of props
Omit<User, 'role'> // exclude props
Record<string, User> // key → value map
Exclude<'a'|'b'|'c', 'a'> // 'b' | 'c'
Extract<'a'|'b'|'c', 'a'|'b'> // 'a' | 'b'
NonNullable<string|null|undefined> // string
ReturnType<typeof fn> // return type of fn
Parameters<typeof fn> // param types as tuple
InstanceType<typeof MyClass>
Awaited<Promise<string>> // string
// Custom utility types
type DeepPartial<T> = {
[K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
};
type Mutable<T> = { -readonly [K in keyof T]: T[K] };
type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
// Usage: Optional<User, 'age' | 'role'> — makes those fields optional
type CreateInput<T> = Omit<T, 'id' | 'createdAt' | 'updatedAt'>;Conditional & Mapped Types
// Conditional types
type IsString<T> = T extends string ? true : false;
type IsArray<T> = T extends any[] ? true : false;
// infer — extract type within conditional
type UnwrapPromise<T> = T extends Promise<infer R> ? R : T;
type ArrayElement<T> = T extends (infer E)[] ? E : never;
type UnwrapPromise<T> = T extends Promise<infer R> ? UnwrapPromise<R> : T;
// UnwrapPromise<Promise<Promise<string>>> → string
// Distributive conditional types
type ToArray<T> = T extends any ? T[] : never;
// ToArray<string | number> → string[] | number[]
// Mapped types
type Nullable<T> = { [K in keyof T]: T[K] | null };
type Getters<T> = { [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K] };
// Getters<{name: string}> → { getName: () => string }
// Template literal types
type EventName<T extends string> = `on${Capitalize<T>}`;
type HttpMethod = 'get' | 'post' | 'put' | 'delete';
type ApiRoute = `/api/${string}`;
// Discriminated unions
type Result<T> =
| { success: true; data: T }
| { success: false; error: string };
function processResult<T>(result: Result<T>) {
if (result.success) {
console.log(result.data); // T — TypeScript knows this
} else {
console.log(result.error); // string
}
}Decorators & Advanced Patterns
// satisfies operator (TS 4.9) — validate type without widening
const palette = {
red: [255, 0, 0],
green: '#00ff00',
} satisfies Record<string, string | number[]>;
palette.red.map(...) // OK — type is number[], not string | number[]
// const assertion
const routes = ['/', '/about', '/contact'] as const;
type Route = typeof routes[number]; // '/' | '/about' | '/contact'
const config = { host: 'localhost', port: 3000 } as const;
// config.port is 3000 (literal), not number
// Function overloads
function parse(val: string): number;
function parse(val: number): string;
function parse(val: string | number): string | number {
return typeof val === 'string' ? parseInt(val) : val.toString();
}
// Module augmentation
declare module 'express' {
interface Request {
user?: { id: string; role: string };
}
}
// Branded types — prevent mixing compatible primitives
type UserId = string & { readonly __brand: 'UserId' };
type OrderId = string & { readonly __brand: 'OrderId' };
const toUserId = (id: string): UserId => id as UserId;
// Can't pass OrderId where UserId expected