| name | typescript |
| description | Use when writing advanced TypeScript — creating utility types, narrowing unions, resolving tricky type errors, configuring the compiler for strict mode, building branded types, or augmenting third-party module types. |
TypeScript — Advanced Type Patterns
Deep TypeScript patterns for writing expressive, safe, self-documenting code.
When to Activate
- Designing generic utilities, API clients, or typed event systems
- Using
infer, conditional types, or mapped types
- Narrowing union types safely without casting
- Configuring
tsconfig.json for strict mode or path aliases
- Augmenting third-party module types
- Building branded/nominal types for domain values
Utility Types (Built-in)
interface User {
id: string;
name: string;
email: string;
role: "admin" | "user";
createdAt: Date;
}
type UpdateUserDto = Partial<User>;
type FullUser = Required<Partial<User>>;
type UserSummary = Pick<User, "id" | "name">;
type CreateUserDto = Omit<User, "id" | "createdAt">;
type FrozenUser = Readonly<User>;
type RolePermissions = Record<User["role"], string[]>;
type AdminRole = Extract<User["role"], "admin">;
type NonAdmin = Exclude<User["role"], "admin">;
type SafeId = NonNullable<string | null | undefined>;
type Handler = (req: Request, res: Response) => void;
type HandlerReturn = ReturnType<Handler>;
type HandlerParams = Parameters<Handler>;
type Resolved = Awaited<Promise<Promise<string>>>;
Generics
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const name = getProperty(user, "name");
interface ApiResponse<T = unknown> {
data: T;
status: number;
message: string;
}
class Stack<T> {
private items: T[] = [];
push(item: T): void { this.items.push(item); }
pop(): T | undefined { return this.items.pop(); }
peek(): T | undefined { return this.items.at(-1); }
}
function zip<A, B>(as: A[], bs: B[]): [A, B][] {
return as.map((a, i) => [a, bs[i]]);
}
<T> = T <infer U> ? U : T;
<T> = T (infer U)[] ? U : T;
A = <<>>;
B = <[]>;
Conditional Types
type IsString<T> = T extends string ? true : false;
type Flatten<T> = T extends (infer U)[] ? U : T;
type F = Flatten<string[] | number | boolean[]>;
type IsEqual<A, B> = [A] extends [B] ? ([B] extends [A] ? true : false) : false;
type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type DeepPartial<T> = T extends object
? { [K in keyof T]?: DeepPartial<T[K]> }
: T;
type RequiredKeys<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>;
type UserWithRequiredEmail = RequiredKeys<<>, >;
Mapped Types
type Optional<T> = { [K in keyof T]?: T[K] };
type Nullable<T> = { [K in keyof T]: T[K] | null };
type Stringify<T> = { [K in keyof T]: string };
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
type UserGetters = Getters<User>;
type PickByValue<T, V> = {
[K in keyof T as T[K] extends V ? K : never]: T[K];
};
type StringFields = PickByValue<User, string>;
type Mutable<T> = { -readonly [K in keyof T]: T[K] };
type DefinedFields<T> = { [K in keyof T]-?: T[K] };
Template Literal Types
type EventName = "click" | "focus" | "blur";
type HandlerName = `on${Capitalize<EventName>}`;
type CSSProperty = "margin" | "padding";
type CSSDirection = "Top" | "Right" | "Bottom" | "Left";
type CSSLonghand = `${CSSProperty}${CSSDirection}`;
type ExtractRouteParams<T extends string> =
T extends `${string}:${infer Param}/${infer Rest}`
? Param | ExtractRouteParams<`/${Rest}`>
: T extends `${string}:${infer Param}`
? Param
: never;
type Params = ExtractRouteParams<"/users/:userId/orders/:orderId">;
= {
: { : ; : };
: { : };
: { : ; : };
};
on<K keyof >(
: K,
: ,
): ;
(, { ... });
Discriminated Unions
The most important pattern for modeling states that must not be mixed.
type ApiState<T> =
| { status: "idle" }
| { status: "loading" }
| { status: "success"; data: T }
| { status: "error"; error: string };
function render<T>(state: ApiState<T>) {
switch (state.status) {
case "idle": return "Waiting...";
case "loading": return "Loading...";
case "success": return state.data;
case "error": return state.error;
}
}
type Result<T, E = Error> =
| { ok: true; value: T }
| { ok: false; error: E };
async function fetchUser(id: ): <<>> {
{
user = db.(id);
{ : , : user };
} (e) {
{ : , : e };
}
}
result = ();
(result.) {
.(result..);
} {
.(result..);
}
Type Narrowing
function process(val: string | number) {
if (typeof val === "string") val.toUpperCase();
else val.toFixed(2);
}
function handle(err: unknown) {
if (err instanceof Error) console.error(err.message);
if (err instanceof TypeError) console.error("Type error:", err.message);
}
type Cat = { meow: () => void };
type Dog = { bark: () => void };
function makeSound(animal: Cat | Dog) {
if ("meow" animal) animal.();
animal.();
}
(): value is {
(
value === &&
value !== &&
value &&
value
);
}
assertDefined<T>(: T | | ): asserts val is T {
(val == ) ();
}
(user);
user.;
satisfies Operator (TypeScript 4.9+)
Validates a value matches a type without widening the inferred type.
const palette = {
red: [255, 0, 0],
green: "#00ff00",
blue: [0, 0, 255],
} satisfies Record<string, string | number[]>;
palette.red.map(v => v * 2);
palette.green.toUpperCase();
Branded / Nominal Types
Prevent mixing semantically different string values at compile time.
type Brand<T, B extends string> = T & { readonly __brand: B };
type UserId = Brand<string, "UserId">;
type OrderId = Brand<string, "OrderId">;
function createUserId(id: string): UserId {
return id as UserId;
}
function getUser(id: UserId): User { ... }
const userId = createUserId("abc-123");
const orderId = "xyz-456" as OrderId;
getUser(userId);
getUser(orderId);
getUser("raw");
Declaration Merging and Module Augmentation
declare global {
namespace Express {
interface Request {
user?: User;
requestId: string;
}
}
}
declare module "some-library" {
interface SomeClass {
myCustomMethod(): void;
}
}
declare global {
interface Window {
analytics: AnalyticsInstance;
}
}
tsconfig.json (Strict Setup)
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"lib": ["ES2022", "DOM"],
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"outDir": "./dist",
Common Patterns
function assertNever(x: never): never {
throw new Error(`Unhandled case: ${JSON.stringify(x)}`);
}
switch (status) {
case "active": return handleActive();
case "inactive": return handleInactive();
default: return assertNever(status);
}
function freeze<T>(obj: T): Readonly<T> {
return Object.freeze(obj);
}
class QueryBuilder<T> {
private filters: Partial<T> = {};
where<K extends keyof T>(key: K, value: T[K]): this {
this.filters[key] = value;
return this;
}
build(): Partial<T> { return .; }
}
= {
: ,
: ,
: ,
} ;
= ( )[keyof ];
Red Flags
any instead of unknown for external data — any disables all type checking on a value and everything it touches; use unknown for data of uncertain shape and narrow it with type guards before use
- Type assertions (
as) instead of type guards — value as User tells the compiler to trust you without verification; if the shape is wrong at runtime, you get silent data corruption rather than a type error; use isUser(value) type guards
// @ts-ignore or // @ts-expect-error as a long-term fix — suppression comments hide real type problems; investigate the root cause and fix the types or the code
strict: false in tsconfig — without strict mode, null and undefined escape into typed values silently; enable strict: true from project start; retrofitting it later costs weeks
- Missing exhaustive check in
switch/match — a switch over a union without an assertNever default compiles successfully when a new union member is added, silently falling through; always add assertNever(x) in the default case
noUncheckedIndexedAccess disabled — arr[i] returns T instead of T | undefined, hiding off-by-one errors; enable this flag and handle the undefined case explicitly
- Widening an inferred type with an explicit annotation —
const routes: string[] = ["/home", "/users"] loses the literal types; use as const or satisfies to preserve precision while still validating the shape
Checklist