Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
A direct command skips the review prompt. Inspect the source before running it.
Production-grade TypeScript patterns for building type-safe, maintainable, and scalable applications. This skill covers generics, utility types, conditional types, discriminated unions, branded types, and more.
1. Advanced Generics with Constraints
Basic Generic Constraints
// Constrain T to objects that have a .length propertyfunction getLength<T extends { length: number }>(item: T): number {
return item.length;
}
();
([, , ]);
getLength
"hello"
// OK - string has .length
getLength
1
2
3
// OK - array has .length
// getLength(42); // Error - number has no .length
Multiple Constraints with Intersection
interfaceHasId {
id: string;
}
interfaceHasTimestamp {
createdAt: Date;
updatedAt: Date;
}
// T must satisfy both interfacesfunction updateEntity<T extendsHasId & HasTimestamp>(
entity: T,
updates: Partial<Omit<T, "id" | "createdAt">>
): T {
return { ...entity, ...updates, updatedAt: newDate() };
}
keyof Constraint Pattern
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { name: "Alice", age: 30, email: "alice@example.com" };
const name = getProperty(user, "name"); // type: stringconst age = getProperty(user, "age"); // type: number// getProperty(user, "phone"); // Error: "phone" not in keyof user
Generic Factory Pattern
interfaceConstructor<T> {
new (...args: any[]): T;
}
function createInstance<T>(ctor: Constructor<T>, ...args: any[]): T {
returnnewctor(...args);
}
classUserService {
constructor(publicapiUrl: string) {}
}
const service = createInstance(UserService, "https://api.example.com");
// service is typed as UserService
2. Built-in Utility Types
Pick, Omit, Partial, Required
interfaceUser {
id: string;
name: string;
email: string;
avatar?: string;
role: "admin" | "user" | "moderator";
preferences: {
theme: "light" | "dark";
notifications: boolean;
};
}
// Pick only the fields you needtypeUserSummary = Pick<User, "id" | "name" | "avatar">;
// Omit sensitive fieldstypePublicUser = Omit<User, "email" | "preferences">;
// Make all fields optional (useful for update payloads)typeUserUpdate = Partial<Omit<User, "id">>;
// Make optional fields requiredtypeCompleteUser = Required<User>;
// avatar is now required
Record, Extract, Exclude
// Record: create an object type with known keystypeUserRole = "admin" | "user" | "moderator";
typeRolePermissions = Record<UserRole, string[]>;
constpermissions: RolePermissions = {
admin: ["read", "write", "delete", "manage-users"],
user: ["read", "write"],
moderator: ["read", "write", "delete"],
};
// Extract: pull types from a union that matchtypeNumericEvent = Extract<"click" | "scroll" | "keypress" | 42, string>;
// Result: "click" | "scroll" | "keypress"// Exclude: remove types from a uniontypeNonAdminRole = Exclude<UserRole, "admin">;
// Result: "user" | "moderator"
typeIsString<T> = T extendsstring ? true : false;
type A = IsString<"hello">; // truetype B = IsString<42>; // false
Extracting Types with infer
// Extract the return type of a promisetypeUnwrapPromise<T> = T extendsPromise<infer U> ? U : T;
typeResult = UnwrapPromise<Promise<string>>; // stringtypePlain = UnwrapPromise<number>; // number// Extract element type from an arraytypeElementOf<T> = T extends (infer E)[] ? E : never;
typeItem = ElementOf<string[]>; // stringtypeNested = ElementOf<number[]>; // number// Extract function argumentstypeFirstArg<T> = T extends (first: infer F, ...rest: any[]) => any ? F : never;
typeArg = FirstArg<(name: string, age: number) =>void>; // string
Distributive Conditional Types
// Conditional types distribute over unions automaticallytypeToArray<T> = T extendsany ? T[] : never;
typeResult = ToArray<string | number>;
// string[] | number[] (NOT (string | number)[])// Prevent distribution with wrapping in tupletypeToArrayNonDist<T> = [T] extends [any] ? T[] : never;
typeResult2 = ToArrayNonDist<string | number>;
// (string | number)[]
Recursive Conditional Types
// Deeply unwrap nested promisestypeDeepUnwrap<T> = T extendsPromise<infer U> ? DeepUnwrap<U> : T;
typeDeep = DeepUnwrap<Promise<Promise<Promise<string>>>>; // string// Deep partial - make all nested properties optionaltypeDeepPartial<T> = T extendsobject
? { [K in keyof T]?: DeepPartial<T[K]> }
: T;
interfaceConfig {
server: {
host: string;
port: number;
ssl: {
enabled: boolean;
cert: string;
};
};
}
typePartialConfig = DeepPartial<Config>;
// All nested fields are now optional
4. Mapped Types and Template Literal Types
Custom Mapped Types
// Make all properties readonlytypeImmutable<T> = {
readonly [K in keyof T]: T[K] extendsobject ? Immutable<T[K]> : T[K];
};
// Make all properties nullabletypeNullable<T> = {
[K in keyof T]: T[K] | null;
};
// Add a prefix to all keystypePrefixed<T, P extendsstring> = {
[K in keyof T as`${P}${Capitalize<string & K>}`]: T[K];
};
interfaceFormData {
name: string;
email: string;
}
typeOnChangeHandlers = Prefixed<FormData, "onChange">;
// { onChangeName: string; onChangeEmail: string }
// This function ensures all union variants are handled at compile timefunctionassertNever(value: never): never {
thrownewError(`Unhandled discriminated union member: ${JSON.stringify(value)}`);
}
typeShape =
| { kind: "circle"; radius: number }
| { kind: "rectangle"; width: number; height: number }
| { kind: "triangle"; base: number; height: number };
functionarea(shape: Shape): number {
switch (shape.kind) {
case"circle":
returnMath.PI * shape.radius ** 2;
case"rectangle":
return shape.width * shape.height;
case"triangle":
return (shape.base * shape.height) / 2;
default:
// If a new variant is added to Shape but not handled here,// TypeScript will produce a compile error on this line.returnassertNever(shape);
}
}
// Problemconststatus: "active" | "inactive" = "active";
let mutable = status;
// mutable is inferred as string, not the literal type// Fix: use const assertion or explicit typeletmutable: typeof status = status;
// ORconst statuses = ["active", "inactive"] asconst;
typeStatus = (typeof statuses)[number]; // "active" | "inactive"
TS2345: Argument of type 'X' is not assignable to parameter of type 'Y'
// Problem: passing an object literal with extra propertiesinterfaceOptions {
timeout: number;
}
functionconfigure(opts: Options) {}
// Fix: use a variable (excess property check only applies to literals)const opts = { timeout: 5000, retries: 3 };
configure(opts); // OK - no excess property check on variables
TS7053: Element implicitly has an 'any' type (index access)
// Problemconstconfig: Record<string, unknown> = {};
const value = config["key"]; // unknown// Fix: use a type guard or assertionfunctiongetString(obj: Record<string, unknown>, key: string): string {
const val = obj[key];
if (typeof val !== "string") {
thrownewError(`Expected string at key "${key}"`);
}
return val;
}
TS2339: Property does not exist on type
// Problem with union typestypeResult = { ok: true; value: string } | { ok: false; error: string };
functionhandle(result: Result) {
// result.value // Error: property doesn't exist on { ok: false; ... }// Fix: narrow the type firstif (result.ok) {
console.log(result.value); // OK after narrowing
} else {
console.log(result.error);
}
}
11. Declaration Merging and Module Augmentation
Interface Merging
// Extend a third-party library's typesdeclaremodule"express" {
interfaceRequest {
user?: {
id: string;
role: string;
};
requestId: string;
}
}
// Now express Request has your custom fieldsimport { Request } from"express";
functionhandler(req: Request) {
console.log(req.user?.id); // OKconsole.log(req.requestId); // OK
}
Global Augmentation
// Add custom properties to globalThisdeclareglobal {
interfaceWindow {
__APP_CONFIG__: {
apiUrl: string;
environment: "development" | "staging" | "production";
};
}
// Add utility to Array prototype (declaration only)interfaceArray<T> {
groupBy<K extendsstring>(fn: (item: T) => K): Record<K, T[]>;
}
}
export {}; // Required to make this a module
{"compilerOptions":{// Enable all strict checks"strict":true,// Individual flags (all enabled by "strict": true)"noImplicitAny":true,"strictNullChecks":true,"strictFunctionTypes":true,"strictBindCallApply":true,"strictPropertyInitialization":true,"noImplicitThis":true,"alwaysStrict":true,// Additional strictness beyond "strict""noUncheckedIndexedAccess":true,// arr[0] is T | undefined"noImplicitReturns":true,"noFallthroughCasesInSwitch":true,"noImplicitOverride":true,"exactOptionalPropertyTypes":true,"noPropertyAccessFromIndexSignature":true,// Module and target"module":"NodeNext","moduleResolution":"NodeNext","target":"ES2022","lib":["ES2022"],// Output"declaration":true,"declarationMap":true,"sourceMap":true,"outDir":"./dist","rootDir":"./src"},"include":["src/**/*"],"exclude":["node_modules","dist","**/*.test.ts"]}
Handling Strict Null Checks
// With strictNullChecks enabledfunctiongetUser(id: string): User | undefined {
return users.get(id);
}
// Option 1: Early return guardfunctiongetUserName(id: string): string {
const user = getUser(id);
if (!user) {
thrownewError(`User ${id} not found`);
}
return user.name; // user is narrowed to User
}
// Option 2: Non-null assertion (use sparingly, only when you are certain)functionunsafeGetName(id: string): string {
returngetUser(id)!.name;
}
// Option 3: Optional chaining with nullish coalescingfunctionsafeGetName(id: string): string {
returngetUser(id)?.name ?? "Anonymous";
}
noUncheckedIndexedAccess Pattern
// With noUncheckedIndexedAccess, array/record access returns T | undefinedconstitems: string[] = ["a", "b", "c"];
const first = items[0]; // string | undefined// Guard before useif (first !== undefined) {
console.log(first.toUpperCase()); // OK
}
// Or use a helperfunction at<T>(arr: T[], index: number): T {
const item = arr[index];
if (item === undefined) {
thrownewRangeError(`Index ${index} out of bounds`);
}
return item;
}
Quick Reference: When to Use What
Pattern
Use Case
Generics
Reusable functions/classes that work with multiple types
Utility types
Transform existing types (pick fields, make optional, etc.)
Conditional types
Types that depend on other types at compile time
Mapped types
Transform all properties of a type systematically
Template literals
Generate string literal union types
Discriminated unions
Model states, events, actions with tagged variants