TypeScript Deep Dive: Advanced Types and Patterns (2026)
You know the basics of TypeScript. Now let's unlock the real power — types that catch bugs at compile time you didn't even know existed.
Utility Types in Practice
// You use these every day. Here's how they actually work:
// Partial<T> — Make all properties optional
interface User {
id: string;
name: string;
email: string;
role: 'admin' | 'user';
}
type UserUpdate = Partial<User>;
// { id?: string; name?: string; email?: string; role?: 'admin'|'user' }
// Real-world: Update function that accepts partial data
function updateUser(id: string, updates: Partial<User>): User {
const existing = getUserFromDb(id);
return { ...existing, ...updates }; // Type-safe merge
}
// Required<T> — Opposite of Partial
type RequiredUser = Required<Partial<User>>;
// All properties become required again
// Omit<T, K> — Remove specific keys
type CreateUserInput = Omit<User, 'id'>;
// { name: string; email: string; role: 'admin'|'user' } — no id needed
// Pick<T, K> — Keep only specific keys
type PublicUser = Pick<User, 'name' | 'role'>;
// { name: string; role: 'admin'|'user' }
// Record<K, V> — Dictionary type
const rolePermissions: Record<string, string[]> = {
admin: ['read', 'write', 'delete'],
user: ['read'],
};
// rolePermissions['admin'] → string[]
// rolePermissions['unknown'] → string[] (no error!)
// Better: Use as const + specific key type
type Role = 'admin' | 'user' | 'moderator';
const permissions: Record<Role, string[]> = {
admin: ['*'],
user: ['read'],
moderator: ['read', 'write'],
};
// Extract<T, U> — Extract types matching a condition
type StringKeys<T> = Extract<keyof T, string>; // Only string keys
// Exclude<T, U> — Opposite of Extract
type NonFunctionKeys<T> = Exclude<keyof T, Function>;
// ReturnType<T> — Get return type of a function
function fetchData(url: string): Promise<{ data: unknown; status: number }> {
return fetch(url).then(r => r.json());
}
type FetchResult = ReturnType<typeof fetchData>;
// Promise<{ data: unknown; status: number }>
// Parameters<T> — Get parameter types as tuple
type FetchParams = Parameters<typeof fetchData>;
// [url: string]
// Awaited<T> — Unwrap Promise (TypeScript 4.5+)
type ResolvedData = Awaited<FetchResult>;
// { data: unknown; status: number }
Conditional Types
// The most powerful TypeScript feature you might not be using enough
// Basic conditional: T extends U ? X : Y
type IsString<T> = T extends string ? true : false;
type A = IsString<'hello'>; // true
type B = IsString<number>; // false
// Practical: Extract non-nullable fields
type NonNullableFields<T> = {
[K in keyof T]: null extends T[K] ? never : K
}[keyof T];
interface UserData {
name: string;
email: string | null;
phone?: string;
age: number | undefined;
}
type RequiredFields = NonNullableFields<UserData>;
// "name" (only name is guaranteed non-nullable)
// Practical: Deep Partial (recursive)
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};
type PartialConfig = DeepPartial<{
database: {
host: string;
port: number;
ssl: { enabled: boolean; cert: string };
};
}>;
// All nested properties are now optional!
// Practical: Flatten nested object paths
type Paths<T, P extends string = ''> = T extends object
? { [K in keyof T & string]: Paths<T[K], `${P}${P ? '.' : ''}${K}`> }[keyof T & string]
: P;
type ConfigPaths = Paths<{ db: { host: string; port: number }; api: { url: string } }>;
// "db.host" | "db.port" | "api.url"
// infer keyword — extract parts of a type
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
type Unwrapped = UnwrapPromise<Promise<string>>; // string
type ArrayElement<T> = T extends (infer E)[] ? E : never;
type Elem = ArrayElement<[string, number, boolean]>; // string | number | boolean
// First parameter type of a function
type FirstParam<T> = T extends (...args: [infer F, ...any[]]) => any ? F : never;
Template Literal Types
// Build types from strings — incredibly powerful for APIs
// CSS property names
type CSSProperty = `--${string}`; // Any CSS custom property
// Event naming convention
type EventName = `on${Capitalize<string>}`;
type ClickEvent = EventName<'click'>; // "onClick"
// HTTP methods
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
// API endpoint builder
type ApiPath = `/api/${string}`;
type Endpoint = `${HttpMethod} ${ApiPath}`;
// "GET /api/users" | "POST /api/users" | ...
// Path-based routing (like Express)
type Route =
| `GET /api/users`
| `GET /api/users/:id`
| `POST /api/users`
| `PUT /api/users/:id`
| `DELETE /api/users/:id`;
// Extract path params from route
type PathParams<T extends string> =
T includes `:${infer Param}`
? Param | PathParams<T.replace(`:${Param}`, '')>
: never;
type UsersIdParams = PathParams<'/api/users/:id'>; // "id"
// Object keys from template literals
type EventHandler = {
[K in `on${Capitalize<string>`}]?: (...args: any[]) => void;
};
// Uppercase/Lowercase/Capitalize/Uncapitalize
type SnackCase = Lowercase<'HelloWorld'>; // "helloworld"
type CamelCase = Capitalize<'hello world'>; // "Hello world"
// String manipulation for validation
type ValidColor = `#${string${number}}`; // Hex color pattern
type CssSize = `${number}px` | `${number}%` | `auto` | `fit-content`;
Mapped Types with Key Remapping
// TypeScript 4.1+ allows remapping keys in mapped types
// Getter/setter pairs
type Accessors<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
} & {
[K in keyof T as `set${Capitalize<string & K>}`]: (value: T[K]) => void
};
type UserAccessors = Accessors<{ name: string; age: number }>;
// {
// getName: () => string;
// setName: (value: string) => void;
// getAge: () => number;
// setAge: (value: number) => void;
// }
// Filter by key pattern
type OnlyStringKeys<T> = {
[K in keyof T as T[K] extends string ? K : never]: T[K]
};
// Create readonly version
type Immutable<T> = {
readonly [K in keyof T]: T[K] extends object ? Immutable<T[K]> : T[K]
};
// Optionalize nullable fields
type OptionalNullable<T> = {
[K in keyof T]: null extends T[K] ? T[K] | undefined : T[K]
};
// Branded types (nominal typing hack)
type Brand<T, B extends string> = T & { __brand: B };
type UserId = Brand<string, 'UserId'>;
type OrderId = Brand<string, 'OrderId'>;
function createUserId(id: string): UserId {
return id as UserId; // Only factory can create!
}
function getUser(id: UserId) { /* ... */ }
function getOrder(id: OrderId) { /* ... */ */
const uid = createUserId('abc123');
getUser(uid); // ✅ Works
getOrder(uid); // ❌ Error! UserId is not OrderId
// Even though both are strings at runtime, TypeScript treats them as different types
Discriminated Unions for State Machines
// This is THE pattern for handling complex state
type RequestState<T> =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: T; timestamp: number }
| { status: 'error'; error: Error; retryCount: number };
// Every state is explicit — no undefined is possible
function handleState<T>(state: RequestState<T>) {
switch (state.status) {
case 'idle':
console.log('Ready to fetch');
break;
case 'loading':
console.log('Loading...');
break;
case 'success':
// TypeScript KNOWS data exists here
console.log(`Got data at ${state.timestamp}:`, state.data);
break;
case 'error':
// TypeScript KNOWS error and retryCount exist here
console.log(`Error (${state.retryCount} retries):`, state.error.message);
if (state.retryCount < 3) {
retry(); // Safe to call
}
break;
default:
// Exhaustive check! If you add a new state, TS errors here.
const _exhaustive: never = state;
throw new Error(`Unknown state: ${_exhaustive}`);
}
}
// Recursive discriminated union (for nested structures)
type JsonValue =
| string
| number
| boolean
| null
| JsonObject
| JsonArray;
interface JsonObject {
[key: string]: JsonValue;
}
interface JsonArray extends Array<JsonValue> {}
// Parse JSON with full type safety
const config: JsonValue = JSON.parse(readFile('config.json'));
if (typeof config === 'object' && config !== null && 'database' in config) {
const db = config.database as JsonObject;
if (typeof db.host === 'string') {
console.log(db.host); // Fully narrowed!
}
}
Error Handling Patterns
// Result type — functional error handling
type Result<T, E = AppError> =
| { success: true; value: T }
| { success: false; error: E };
// Usage: No try/catch at call site!
async function safeFetch<T>(url: string): Promise<Result<T>> {
try {
const res = await fetch(url);
if (!res.ok) {
return { success: false, error: new HttpError(res.status, res.statusText) };
}
const data = await res.json() as T;
return { success: true, value: data };
} catch (err) {
return { success: false, error: new NetworkError(err instanceof Error ? err.message : String(err)) };
}
}
// Call site is clean:
const result = await safeFetch<User>('/api/user/123');
if (result.success) {
console.log(result.value.name); // TypeScript knows value exists
} else {
handleAppError(result.error); // TypeScript knows error exists
}
// Assert functions (narrowing via assertion)
function assert(condition: unknown, msg?: string): asserts condition {
if (!condition) throw new AssertionError(msg || 'Assertion failed');
}
function assertDefined<T>(val: T, msg?: string): asserts val is NonNullable<T> {
if (val === undefined || val === null) throw new AssertionError(msg || 'Value is defined');
}
// Satisfies operator — check type without changing it
type Config = {
port: number;
host: string;
tls?: boolean;
};
const cfg = {
port: 3000,
host: 'localhost',
// Missing tls? That's OK, it's optional
} satisfies Config;
// But this would fail:
const badCfg = {
port: '3000', // ❌ string, not number!
host: 'localhost',
} satisfies Config; // Type error!
// Const assertions — lock down literal types
const ROUTES = {
HOME: '/',
ABOUT: '/about',
LOGIN: '/login',
} as const;
type RouteKey = typeof ROUTES[keyof typeof ROUTES]; // "/" | "/about" | "/login"
type RouteName = keyof typeof ROUTES; // "HOME" | "ABOUT" | "LOGIN"
function navigate(route: RouteName) {
window.location.href = ROUTES[route]; // Type-safe routing!
}
navigate('HOME'); // ✅
navigate('PROFILE'); // ❌ Error!
What's your favorite advanced TypeScript pattern? Which one here is new to you?
Follow @armorbreak for more practical developer guides.
Top comments (0)