Introduction
TypeScript's type system is far more powerful than most developers use. Beyond basic interfaces and generics, it supports full type-level computation — you can write programs that run at compile time to validate your runtime contracts.
Conditional Types
Conditional types follow the pattern T extends U ? X : Y. They let you write types that branch based on other types, enabling powerful inference utilities.
"hl-keyword">class="hl-comment">// Extract the resolved "hl-keyword">type "hl-keyword">from a "hl-type">Promise
"hl-keyword">type Awaited<T> = T "hl-keyword">extends "hl-type">Promise<"hl-keyword">infer R> ? R : T;
"hl-keyword">type A = Awaited<"hl-type">Promise<"hl-type">string>>; "hl-keyword">class="hl-comment">// "hl-type">string
"hl-keyword">type B = Awaited<"hl-type">number>; "hl-keyword">class="hl-comment">// "hl-type">number
"hl-keyword">class="hl-comment">// Extract "hl-keyword">function "hl-keyword">return "hl-keyword">type
"hl-keyword">type ReturnType<T> = T "hl-keyword">extends (...args: "hl-type">any[]) => "hl-keyword">infer R ? R : "hl-keyword">never;
"hl-keyword">async "hl-keyword">function fetchUser() {
"hl-keyword">return { id: '1', name: 'Anant' };
}
"hl-keyword">type User = Awaited<ReturnType<"hl-keyword">typeof fetchUser>>;
"hl-keyword">class="hl-comment">// User = { id: "hl-type">string; name: "hl-type">string }
"hl-keyword">class="hl-comment">// Distributive conditional types
"hl-keyword">type NonNullable<T> = T "hl-keyword">extends "hl-type">null | "hl-type">undefined ? "hl-keyword">never : T;
"hl-keyword">type Safe = NonNullable<"hl-type">string | "hl-type">null | "hl-type">undefined>; "hl-keyword">class="hl-comment">// "hl-type">string
Mapped Types
Mapped types iterate over the keys of another type to produce a new shape. They are the foundation of utility types like Partial, Required, and Readonly.
"hl-keyword">class="hl-comment">// Build Partial<T> "hl-keyword">from scratch
"hl-keyword">type MyPartial<T> = {
[K "hl-keyword">in "hl-keyword">keyof T]?: T[K];
};
"hl-keyword">class="hl-comment">// Make specific keys optional, rest required
"hl-keyword">type PartialBy<T, K "hl-keyword">extends "hl-keyword">keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
"hl-keyword">interface User {
id: "hl-type">string;
name: "hl-type">string;
email: "hl-type">string;
bio: "hl-type">string;
}
"hl-keyword">class="hl-comment">// bio is optional, everything "hl-keyword">else required
"hl-keyword">type UpdateUser = PartialBy<User, 'bio'>;
"hl-keyword">class="hl-comment">// Deep "hl-keyword">readonly — recursively make all nested properties "hl-keyword">readonly
"hl-keyword">type DeepReadonly<T> = {
"hl-keyword">readonly [K "hl-keyword">in "hl-keyword">keyof T]: T[K] "hl-keyword">extends "hl-type">object ? DeepReadonly<T[K]> : T[K];
};
"hl-keyword">type Config = DeepReadonly<{
db: { host: "hl-type">string; port: "hl-type">number };
features: { darkMode: "hl-type">boolean };
}>;
Template Literal Types
TypeScript 4.1 introduced template literal types, letting you construct string union types programmatically. This is incredibly powerful for event systems, CSS properties, and route generation.
"hl-keyword">class="hl-comment">// Generate all CSS dimension properties
"hl-keyword">type Side = 'top' | 'right' | 'bottom' | 'left';
"hl-keyword">type CSSProperty = `margin-${Side}` | `padding-${Side}` | `border-${Side}-width`;
"hl-keyword">class="hl-comment">// "margin-top" | "margin-right" | ... | "border-left-width"
"hl-keyword">class="hl-comment">// Typed event emitter
"hl-keyword">type EventMap = {
'user:created': { id: "hl-type">string; name: "hl-type">string };
'user:deleted': { id: "hl-type">string };
'post:published': { postId: "hl-type">string; authorId: "hl-type">string };
};
"hl-keyword">type EventName = "hl-keyword">keyof EventMap; "hl-keyword">class="hl-comment">// 'user:created' | 'user:deleted' | 'post:published'
"hl-keyword">function emit<E "hl-keyword">extends EventName>(event: E, payload: EventMap[E]): "hl-type">void {
"hl-keyword">class="hl-comment">// fully "hl-keyword">type-safe!
}
emit('user:created', { id: '1', name: 'Anant' }); "hl-keyword">class="hl-comment">// ✅
"hl-keyword">class="hl-comment">// emit('user:created', { wrong: true }); // ❌ compile error
Discriminated Unions & Exhaustive Checks
Discriminated unions are the most ergonomic way to model "one of several shapes" in TypeScript. Pair them with exhaustive checks to catch unhandled cases at compile time.
"hl-keyword">type ApiResult<T> =
| { status: 'success'; data: T }
| { status: 'error'; error: "hl-type">string; code: "hl-type">number }
| { status: 'loading' };
"hl-keyword">class="hl-comment">// Exhaustive "hl-keyword">switch — TypeScript will error "hl-keyword">if you add a "hl-keyword">new status and forget to handle it
"hl-keyword">function renderResult<T>(result: ApiResult<T>): "hl-type">string {
"hl-keyword">switch (result.status) {
"hl-keyword">case 'success': "hl-keyword">return `Data: ${JSON.stringify(result.data)}`;
"hl-keyword">case 'error': "hl-keyword">return `Error ${result.code}: ${result.error}`;
"hl-keyword">case 'loading': "hl-keyword">return 'Loading…';
"hl-keyword">default:
"hl-keyword">class="hl-comment">// This line makes the check exhaustive
"hl-keyword">const _exhaustive: "hl-keyword">never = result;
"hl-keyword">throw "hl-keyword">new Error(`Unhandled status: ${_exhaustive}`);
}
}