jpskill.com
💬 コミュニケーション コミュニティ

typescript-patterns

TypeScriptの高度な型付けやパターンを活用し、複雑な型定義やエラー修正を通じて、堅牢で安全なコード設計を支援するSkill。

📜 元の英語説明(参考)

TypeScript patterns, advanced typing, and type safety. Use when user asks to "write TypeScript generics", "create utility types", "fix TypeScript errors", "type a complex object", "use conditional types", "create type guards", "improve TypeScript types", "design type-safe APIs", "use mapped types", "create branded types", "infer types", "type narrowing", "type variance", "overload signatures", "type assertion", or mentions TypeScript patterns, advanced typing, generic constraints, discriminated unions, template literal types, or type inference.

🇯🇵 日本人クリエイター向け解説

一言でいうと

TypeScriptの高度な型付けやパターンを活用し、複雑な型定義やエラー修正を通じて、堅牢で安全なコード設計を支援するSkill。

※ jpskill.com 編集部が日本のビジネス現場向けに補足した解説です。Skill本体の挙動とは独立した参考情報です。

⚡ おすすめ: コマンド1行でインストール(60秒)

下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。

🍎 Mac / 🐧 Linux
mkdir -p ~/.claude/skills && cd ~/.claude/skills && curl -L -o typescript-patterns.zip https://jpskill.com/download/6139.zip && unzip -o typescript-patterns.zip && rm typescript-patterns.zip
🪟 Windows (PowerShell)
$d = "$env:USERPROFILE\.claude\skills"; ni -Force -ItemType Directory $d | Out-Null; iwr https://jpskill.com/download/6139.zip -OutFile "$d\typescript-patterns.zip"; Expand-Archive "$d\typescript-patterns.zip" -DestinationPath $d -Force; ri "$d\typescript-patterns.zip"

完了後、Claude Code を再起動 → 普通に「動画プロンプト作って」のように話しかけるだけで自動発動します。

💾 手動でダウンロードしたい(コマンドが難しい人向け)
  1. 1. 下の青いボタンを押して typescript-patterns.zip をダウンロード
  2. 2. ZIPファイルをダブルクリックで解凍 → typescript-patterns フォルダができる
  3. 3. そのフォルダを C:\Users\あなたの名前\.claude\skills\(Win)または ~/.claude/skills/(Mac)へ移動
  4. 4. Claude Code を再起動

⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。

🎯 このSkillでできること

下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。

📦 インストール方法 (3ステップ)

  1. 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
  2. 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
  3. 3. 展開してできたフォルダを、ホームフォルダの .claude/skills/ に置く
    • · macOS / Linux: ~/.claude/skills/
    • · Windows: %USERPROFILE%\.claude\skills\

Claude Code を再起動すれば完了。「このSkillを使って…」と話しかけなくても、関連する依頼で自動的に呼び出されます。

詳しい使い方ガイドを見る →
最終更新
2026-05-17
取得日時
2026-05-17
同梱ファイル
1

📖 Skill本文(日本語訳)

※ 原文(英語/中国語)を Gemini で日本語化したものです。Claude 自身は原文を読みます。誤訳がある場合は原文をご確認ください。

[Skill 名] typescript-patterns

高度な TypeScript パターン

型安全で、保守しやすく、スケーラブルなアプリケーションを構築するための、プロダクションレベルの TypeScript パターンです。このスキルでは、ジェネリクス、ユーティリティ型、条件型、判別共用体、ブランド型などを扱います。


1. 制約付きの高度なジェネリクス

基本的なジェネリック制約

// T を .length プロパティを持つオブジェクトに制約します
function getLength<T extends { length: number }>(item: T): number {
  return item.length;
}

getLength("hello");       // OK - string は .length を持ちます
getLength([1, 2, 3]);     // OK - array は .length を持ちます
// getLength(42);          // エラー - number は .length を持ちません

交差型による複数の制約

interface HasId {
  id: string;
}

interface HasTimestamp {
  createdAt: Date;
  updatedAt: Date;
}

// T は両方のインターフェースを満たす必要があります
function updateEntity<T extends HasId & HasTimestamp>(
  entity: T,
  updates: Partial<Omit<T, "id" | "createdAt">>
): T {
  return { ...entity, ...updates, updatedAt: new Date() };
}

keyof 制約パターン

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");   // 型: string
const age = getProperty(user, "age");     // 型: number
// getProperty(user, "phone");            // エラー: "phone" は keyof user に含まれません

ジェネリックファクトリーパターン

interface Constructor<T> {
  new (...args: any[]): T;
}

function createInstance<T>(ctor: Constructor<T>, ...args: any[]): T {
  return new ctor(...args);
}

class UserService {
  constructor(public apiUrl: string) {}
}

const service = createInstance(UserService, "https://api.example.com");
// service は UserService 型として推論されます

2. 組み込みのユーティリティ型

Pick, Omit, Partial, Required

interface User {
  id: string;
  name: string;
  email: string;
  avatar?: string;
  role: "admin" | "user" | "moderator";
  preferences: {
    theme: "light" | "dark";
    notifications: boolean;
  };
}

// 必要なフィールドのみを Pick します
type UserSummary = Pick<User, "id" | "name" | "avatar">;

// 機密性の高いフィールドを Omit します
type PublicUser = Omit<User, "email" | "preferences">;

// すべてのフィールドをオプションにします (更新ペイロードに便利です)
type UserUpdate = Partial<Omit<User, "id">>;

// オプションのフィールドを必須にします
type CompleteUser = Required<User>;
// avatar が必須になりました

Record, Extract, Exclude

// Record: 既知のキーを持つオブジェクト型を作成します
type UserRole = "admin" | "user" | "moderator";

type RolePermissions = Record<UserRole, string[]>;

const permissions: RolePermissions = {
  admin: ["read", "write", "delete", "manage-users"],
  user: ["read", "write"],
  moderator: ["read", "write", "delete"],
};

// Extract: ユニオンから一致する型を抽出します
type NumericEvent = Extract<"click" | "scroll" | "keypress" | 42, string>;
// 結果: "click" | "scroll" | "keypress"

// Exclude: ユニオンから型を削除します
type NonAdminRole = Exclude<UserRole, "admin">;
// 結果: "user" | "moderator"

ReturnType, Parameters, InstanceType

function fetchUser(id: string, options?: { includeProfile: boolean }) {
  return { id, name: "Alice", email: "alice@example.com" };
}

type FetchUserReturn = ReturnType<typeof fetchUser>;
// { id: string; name: string; email: string }

type FetchUserParams = Parameters<typeof fetchUser>;
// [id: string, options?: { includeProfile: boolean }]

class ApiClient {
  baseUrl: string;
  constructor(baseUrl: string) {
    this.baseUrl = baseUrl;
  }
}

type ApiClientInstance = InstanceType<typeof ApiClient>;
// ApiClient

3. 条件型と infer キーワード

基本的な条件型

type IsString<T> = T extends string ? true : false;

type A = IsString<"hello">;  // true
type B = IsString<42>;       // false

infer を使用した型の抽出

// プロミスの戻り値を抽出します
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;

type Result = UnwrapPromise<Promise<string>>;  // string
type Plain = UnwrapPromise<number>;            // number

// 配列から要素の型を抽出します
type ElementOf<T> = T extends (infer E)[] ? E : never;

type Item = ElementOf<string[]>;   // string
type Nested = ElementOf<number[]>; // number

// 関数の引数を抽出します
type FirstArg<T> = T extends (first: infer F, ...rest: any[]) => any ? F : never;

type Arg = FirstArg<(name: string, age: number) => void>; // string

分配条件型

// 条件型はユニオンに対して自動的に分配されます
type ToArray<T> = T extends any ? T[] : never;

type Result = ToArray<string | number>;
// string[] | number[]  ((string | number)[] ではありません)

// タプルでラップして分配を防ぎます
type ToArrayNonDist<T> = [T] extends [any] ? T[] : never;

type Result2 = ToArrayNonDist<string | number>;
// (string | number)[]

再帰的な条件型

// ネストされたプロミスを深くアンラップします
type DeepUnwrap<T> = T extends Promise<infer U> ? DeepUnwrap<U> : T;

type Deep = DeepUnwrap<Promise<Promise<Promise<string>>>>; // string

// ディープパーシャル - すべてのネストされたプロパティをオプションにします
type DeepPartial<T> = T extends object
  ? { [K in keyof T]?: DeepPartial<T[K]> }
  : T;

interface Config {
  server: {
    host: string;
    port: number;
    ssl: {
      enabled: boolean;
      cert: string;
    };
  };
}

type PartialConfig = DeepPartial<Config>;
// すべてのネストされたフィールドがオプションになりました

4. マップ型とテンプレートリテラル型

カスタムマップ型

// すべてのプロパティを readonly にします
type Immutable<T> = {
  readonly [K in keyof T]: T[K] extends object ? Immutable<T[K]> : T[K];
};

// すべてのプロパティを nullable にします
type Nullable<T> = {
  [K in keyof T]: T[K] | null;
};

// すべてのキーにプレフィックスを追加します
type Prefixed<T, P extends string> = {
  [K in keyof T as `${P}${Capitaliz
📜 原文 SKILL.md(Claudeが読む英語/中国語)を展開

Advanced TypeScript Patterns

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 property
function 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

interface HasId {
  id: string;
}

interface HasTimestamp {
  createdAt: Date;
  updatedAt: Date;
}

// T must satisfy both interfaces
function updateEntity<T extends HasId & HasTimestamp>(
  entity: T,
  updates: Partial<Omit<T, "id" | "createdAt">>
): T {
  return { ...entity, ...updates, updatedAt: new Date() };
}

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: string
const age = getProperty(user, "age");     // type: number
// getProperty(user, "phone");            // Error: "phone" not in keyof user

Generic Factory Pattern

interface Constructor<T> {
  new (...args: any[]): T;
}

function createInstance<T>(ctor: Constructor<T>, ...args: any[]): T {
  return new ctor(...args);
}

class UserService {
  constructor(public apiUrl: string) {}
}

const service = createInstance(UserService, "https://api.example.com");
// service is typed as UserService

2. Built-in Utility Types

Pick, Omit, Partial, Required

interface User {
  id: string;
  name: string;
  email: string;
  avatar?: string;
  role: "admin" | "user" | "moderator";
  preferences: {
    theme: "light" | "dark";
    notifications: boolean;
  };
}

// Pick only the fields you need
type UserSummary = Pick<User, "id" | "name" | "avatar">;

// Omit sensitive fields
type PublicUser = Omit<User, "email" | "preferences">;

// Make all fields optional (useful for update payloads)
type UserUpdate = Partial<Omit<User, "id">>;

// Make optional fields required
type CompleteUser = Required<User>;
// avatar is now required

Record, Extract, Exclude

// Record: create an object type with known keys
type UserRole = "admin" | "user" | "moderator";

type RolePermissions = Record<UserRole, string[]>;

const permissions: RolePermissions = {
  admin: ["read", "write", "delete", "manage-users"],
  user: ["read", "write"],
  moderator: ["read", "write", "delete"],
};

// Extract: pull types from a union that match
type NumericEvent = Extract<"click" | "scroll" | "keypress" | 42, string>;
// Result: "click" | "scroll" | "keypress"

// Exclude: remove types from a union
type NonAdminRole = Exclude<UserRole, "admin">;
// Result: "user" | "moderator"

ReturnType, Parameters, InstanceType

function fetchUser(id: string, options?: { includeProfile: boolean }) {
  return { id, name: "Alice", email: "alice@example.com" };
}

type FetchUserReturn = ReturnType<typeof fetchUser>;
// { id: string; name: string; email: string }

type FetchUserParams = Parameters<typeof fetchUser>;
// [id: string, options?: { includeProfile: boolean }]

class ApiClient {
  baseUrl: string;
  constructor(baseUrl: string) {
    this.baseUrl = baseUrl;
  }
}

type ApiClientInstance = InstanceType<typeof ApiClient>;
// ApiClient

3. Conditional Types and the infer Keyword

Basic Conditional Types

type IsString<T> = T extends string ? true : false;

type A = IsString<"hello">;  // true
type B = IsString<42>;       // false

Extracting Types with infer

// Extract the return type of a promise
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;

type Result = UnwrapPromise<Promise<string>>;  // string
type Plain = UnwrapPromise<number>;            // number

// Extract element type from an array
type ElementOf<T> = T extends (infer E)[] ? E : never;

type Item = ElementOf<string[]>;   // string
type Nested = ElementOf<number[]>; // number

// Extract function arguments
type FirstArg<T> = T extends (first: infer F, ...rest: any[]) => any ? F : never;

type Arg = FirstArg<(name: string, age: number) => void>; // string

Distributive Conditional Types

// Conditional types distribute over unions automatically
type ToArray<T> = T extends any ? T[] : never;

type Result = ToArray<string | number>;
// string[] | number[]  (NOT (string | number)[])

// Prevent distribution with wrapping in tuple
type ToArrayNonDist<T> = [T] extends [any] ? T[] : never;

type Result2 = ToArrayNonDist<string | number>;
// (string | number)[]

Recursive Conditional Types

// Deeply unwrap nested promises
type DeepUnwrap<T> = T extends Promise<infer U> ? DeepUnwrap<U> : T;

type Deep = DeepUnwrap<Promise<Promise<Promise<string>>>>; // string

// Deep partial - make all nested properties optional
type DeepPartial<T> = T extends object
  ? { [K in keyof T]?: DeepPartial<T[K]> }
  : T;

interface Config {
  server: {
    host: string;
    port: number;
    ssl: {
      enabled: boolean;
      cert: string;
    };
  };
}

type PartialConfig = DeepPartial<Config>;
// All nested fields are now optional

4. Mapped Types and Template Literal Types

Custom Mapped Types

// Make all properties readonly
type Immutable<T> = {
  readonly [K in keyof T]: T[K] extends object ? Immutable<T[K]> : T[K];
};

// Make all properties nullable
type Nullable<T> = {
  [K in keyof T]: T[K] | null;
};

// Add a prefix to all keys
type Prefixed<T, P extends string> = {
  [K in keyof T as `${P}${Capitalize<string & K>}`]: T[K];
};

interface FormData {
  name: string;
  email: string;
}

type OnChangeHandlers = Prefixed<FormData, "onChange">;
// { onChangeName: string; onChangeEmail: string }

Template Literal Types

type HTTPMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
type APIVersion = "v1" | "v2";
type Resource = "users" | "posts" | "comments";

// Generate all route combinations
type APIRoute = `/${APIVersion}/${Resource}`;
// "/v1/users" | "/v1/posts" | "/v1/comments" | "/v2/users" | ...

// Event handler types
type DOMEvent = "click" | "focus" | "blur" | "input";
type EventHandler = `on${Capitalize<DOMEvent>}`;
// "onClick" | "onFocus" | "onBlur" | "onInput"

// CSS unit types
type CSSUnit = "px" | "rem" | "em" | "vh" | "vw" | "%";
type CSSValue = `${number}${CSSUnit}`;

const width: CSSValue = "100px";   // OK
const height: CSSValue = "50vh";   // OK
// const bad: CSSValue = "auto";   // Error

Key Remapping with as

// Create getter functions for each property
type Getters<T> = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};

interface Person {
  name: string;
  age: number;
}

type PersonGetters = Getters<Person>;
// { getName: () => string; getAge: () => number }

// Filter keys by value type
type StringKeysOnly<T> = {
  [K in keyof T as T[K] extends string ? K : never]: T[K];
};

interface Mixed {
  name: string;
  age: number;
  email: string;
  active: boolean;
}

type OnlyStrings = StringKeysOnly<Mixed>;
// { name: string; email: string }

5. Discriminated Unions and Exhaustive Checking

Basic Discriminated Union

interface LoadingState {
  status: "loading";
}

interface SuccessState<T> {
  status: "success";
  data: T;
}

interface ErrorState {
  status: "error";
  error: {
    message: string;
    code: number;
  };
}

type RequestState<T> = LoadingState | SuccessState<T> | ErrorState;

function renderState<T>(state: RequestState<T>): string {
  switch (state.status) {
    case "loading":
      return "Loading...";
    case "success":
      return `Data: ${JSON.stringify(state.data)}`;
    case "error":
      return `Error ${state.error.code}: ${state.error.message}`;
  }
}

Exhaustive Checking with never

// This function ensures all union variants are handled at compile time
function assertNever(value: never): never {
  throw new Error(`Unhandled discriminated union member: ${JSON.stringify(value)}`);
}

type Shape =
  | { kind: "circle"; radius: number }
  | { kind: "rectangle"; width: number; height: number }
  | { kind: "triangle"; base: number; height: number };

function area(shape: Shape): number {
  switch (shape.kind) {
    case "circle":
      return Math.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.
      return assertNever(shape);
  }
}

Action/Event Pattern (Redux-style)

type Action =
  | { type: "ADD_TODO"; payload: { text: string } }
  | { type: "TOGGLE_TODO"; payload: { id: number } }
  | { type: "REMOVE_TODO"; payload: { id: number } }
  | { type: "SET_FILTER"; payload: { filter: "all" | "active" | "completed" } };

interface TodoState {
  todos: Array<{ id: number; text: string; completed: boolean }>;
  filter: "all" | "active" | "completed";
}

function todoReducer(state: TodoState, action: Action): TodoState {
  switch (action.type) {
    case "ADD_TODO":
      return {
        ...state,
        todos: [
          ...state.todos,
          { id: Date.now(), text: action.payload.text, completed: false },
        ],
      };
    case "TOGGLE_TODO":
      return {
        ...state,
        todos: state.todos.map((t) =>
          t.id === action.payload.id ? { ...t, completed: !t.completed } : t
        ),
      };
    case "REMOVE_TODO":
      return {
        ...state,
        todos: state.todos.filter((t) => t.id !== action.payload.id),
      };
    case "SET_FILTER":
      return { ...state, filter: action.payload.filter };
    default:
      return assertNever(action as never);
  }
}

6. Type Guards and Type Narrowing

Custom Type Guard Functions

interface Cat {
  type: "cat";
  purrs: boolean;
}

interface Dog {
  type: "dog";
  barks: boolean;
}

type Animal = Cat | Dog;

// Type predicate: `animal is Cat`
function isCat(animal: Animal): animal is Cat {
  return animal.type === "cat";
}

function handleAnimal(animal: Animal) {
  if (isCat(animal)) {
    console.log(animal.purrs); // TypeScript knows this is Cat
  } else {
    console.log(animal.barks); // TypeScript knows this is Dog
  }
}

Assertion Functions

function assertIsString(value: unknown): asserts value is string {
  if (typeof value !== "string") {
    throw new Error(`Expected string, got ${typeof value}`);
  }
}

function processInput(input: unknown) {
  assertIsString(input);
  // After assertion, TypeScript knows input is string
  console.log(input.toUpperCase());
}

in Operator Narrowing

interface APIResponse {
  data: unknown;
}

interface APIError {
  error: string;
  statusCode: number;
}

function handleResponse(res: APIResponse | APIError) {
  if ("error" in res) {
    // TypeScript narrows to APIError
    console.error(`Error ${res.statusCode}: ${res.error}`);
  } else {
    // TypeScript narrows to APIResponse
    console.log(res.data);
  }
}

Narrowing with satisfies

// satisfies validates the type without widening it
type Color = "red" | "green" | "blue";
type ColorMap = Record<Color, string | [number, number, number]>;

const palette = {
  red: [255, 0, 0],
  green: "#00ff00",
  blue: [0, 0, 255],
} satisfies ColorMap;

// palette.red is inferred as [number, number, number], not string | [number, number, number]
const redChannel = palette.red[0]; // OK - TypeScript knows it is a tuple

7. Branded / Opaque Types for Type Safety

Preventing Primitive Type Confusion

// Brand type utility
type Brand<T, B extends string> = T & { readonly __brand: B };

type UserId = Brand<string, "UserId">;
type OrderId = Brand<string, "OrderId">;
type Email = Brand<string, "Email">;

// Constructor functions with validation
function createUserId(id: string): UserId {
  if (!id.match(/^usr_[a-zA-Z0-9]{12}$/)) {
    throw new Error("Invalid user ID format");
  }
  return id as UserId;
}

function createEmail(email: string): Email {
  if (!email.includes("@")) {
    throw new Error("Invalid email format");
  }
  return email as Email;
}

function sendEmail(to: Email, subject: string): void {
  // ...
}

function getUser(id: UserId): void {
  // ...
}

const userId = createUserId("usr_abc123def456");
const email = createEmail("alice@example.com");

sendEmail(email, "Welcome!");  // OK
// sendEmail(userId, "Welcome!"); // Error: UserId is not assignable to Email
// getUser(email);                // Error: Email is not assignable to UserId

Branded Numeric Types

type Celsius = Brand<number, "Celsius">;
type Fahrenheit = Brand<number, "Fahrenheit">;
type Kilometers = Brand<number, "Kilometers">;
type Miles = Brand<number, "Miles">;

function celsiusToFahrenheit(c: Celsius): Fahrenheit {
  return ((c * 9) / 5 + 32) as Fahrenheit;
}

function milesToKilometers(m: Miles): Kilometers {
  return (m * 1.60934) as Kilometers;
}

const temp = 100 as Celsius;
const converted = celsiusToFahrenheit(temp); // Fahrenheit
// celsiusToFahrenheit(100 as Fahrenheit);   // Error: type mismatch

8. Builder Pattern with Types

Type-Safe Builder

interface QueryConfig {
  table: string;
  columns: string[];
  where: string[];
  orderBy: string | null;
  limit: number | null;
}

type RequiredFields = "table" | "columns";

class QueryBuilder<T extends Partial<QueryConfig> = {}> {
  private config: Partial<QueryConfig> = {};

  from<Table extends string>(
    table: Table
  ): QueryBuilder<T & { table: Table }> {
    this.config.table = table;
    return this as any;
  }

  select(
    ...columns: string[]
  ): QueryBuilder<T & { columns: string[] }> {
    this.config.columns = columns;
    return this as any;
  }

  where(condition: string): QueryBuilder<T & { where: string[] }> {
    this.config.where = [...(this.config.where ?? []), condition];
    return this as any;
  }

  orderBy(column: string): this {
    this.config.orderBy = column;
    return this;
  }

  limit(count: number): this {
    this.config.limit = count;
    return this;
  }

  // Only available when required fields are set
  build(
    this: QueryBuilder<Pick<QueryConfig, RequiredFields>>
  ): QueryConfig {
    return {
      table: this.config.table!,
      columns: this.config.columns!,
      where: this.config.where ?? [],
      orderBy: this.config.orderBy ?? null,
      limit: this.config.limit ?? null,
    };
  }
}

// Usage
const query = new QueryBuilder()
  .from("users")
  .select("id", "name", "email")
  .where("active = true")
  .orderBy("name")
  .limit(10)
  .build(); // Only compiles because from() and select() were called

9. Runtime Validation with Zod

Schema Definition and Inference

import { z } from "zod";

// Define schema
const UserSchema = z.object({
  id: z.string().uuid(),
  name: z.string().min(1).max(100),
  email: z.string().email(),
  age: z.number().int().min(0).max(150).optional(),
  role: z.enum(["admin", "user", "moderator"]),
  tags: z.array(z.string()).default([]),
  metadata: z.record(z.string(), z.unknown()).optional(),
  createdAt: z.coerce.date(),
});

// Infer the TypeScript type from the schema
type User = z.infer<typeof UserSchema>;
// {
//   id: string;
//   name: string;
//   email: string;
//   age?: number;
//   role: "admin" | "user" | "moderator";
//   tags: string[];
//   metadata?: Record<string, unknown>;
//   createdAt: Date;
// }

// Validate at runtime
function createUser(input: unknown): User {
  return UserSchema.parse(input); // throws ZodError if invalid
}

// Safe parsing (no throw)
function tryCreateUser(input: unknown): User | null {
  const result = UserSchema.safeParse(input);
  if (result.success) {
    return result.data;
  }
  console.error("Validation errors:", result.error.flatten());
  return null;
}

API Request/Response Validation

const CreateUserRequest = UserSchema.omit({ id: true, createdAt: true });
const UpdateUserRequest = CreateUserRequest.partial();

const APIResponse = <T extends z.ZodType>(dataSchema: T) =>
  z.object({
    success: z.boolean(),
    data: dataSchema,
    meta: z
      .object({
        page: z.number(),
        total: z.number(),
      })
      .optional(),
  });

const UserListResponse = APIResponse(z.array(UserSchema));
type UserListResponse = z.infer<typeof UserListResponse>;

10. Common TypeScript Errors and Fixes

TS2322: Type 'X' is not assignable to type 'Y'

// Problem
const status: "active" | "inactive" = "active";
let mutable = status;
// mutable is inferred as string, not the literal type

// Fix: use const assertion or explicit type
let mutable: typeof status = status;
// OR
const statuses = ["active", "inactive"] as const;
type Status = (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 properties
interface Options {
  timeout: number;
}

function configure(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)

// Problem
const config: Record<string, unknown> = {};
const value = config["key"]; // unknown

// Fix: use a type guard or assertion
function getString(obj: Record<string, unknown>, key: string): string {
  const val = obj[key];
  if (typeof val !== "string") {
    throw new Error(`Expected string at key "${key}"`);
  }
  return val;
}

TS2339: Property does not exist on type

// Problem with union types
type Result = { ok: true; value: string } | { ok: false; error: string };

function handle(result: Result) {
  // result.value  // Error: property doesn't exist on { ok: false; ... }

  // Fix: narrow the type first
  if (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 types
declare module "express" {
  interface Request {
    user?: {
      id: string;
      role: string;
    };
    requestId: string;
  }
}

// Now express Request has your custom fields
import { Request } from "express";
function handler(req: Request) {
  console.log(req.user?.id);     // OK
  console.log(req.requestId);    // OK
}

Global Augmentation

// Add custom properties to globalThis
declare global {
  interface Window {
    __APP_CONFIG__: {
      apiUrl: string;
      environment: "development" | "staging" | "production";
    };
  }

  // Add utility to Array prototype (declaration only)
  interface Array<T> {
    groupBy<K extends string>(fn: (item: T) => K): Record<K, T[]>;
  }
}

export {}; // Required to make this a module

Namespace Merging

// Merge a namespace with a function (overloaded module pattern)
function currency(amount: number): string;
function currency(amount: number, code: string): string;
function currency(amount: number, code?: string): string {
  return `${(code ?? "USD")} ${amount.toFixed(2)}`;
}

namespace currency {
  export const codes = ["USD", "EUR", "GBP", "JPY"] as const;
  export type Code = (typeof codes)[number];
  export function convert(amount: number, from: Code, to: Code): number {
    // conversion logic
    return amount;
  }
}

currency(42);                       // "USD 42.00"
currency.convert(100, "USD", "EUR"); // uses namespace

12. Strict Mode Best Practices

Recommended tsconfig.json Strict Settings

{
  "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 enabled
function getUser(id: string): User | undefined {
  return users.get(id);
}

// Option 1: Early return guard
function getUserName(id: string): string {
  const user = getUser(id);
  if (!user) {
    throw new Error(`User ${id} not found`);
  }
  return user.name; // user is narrowed to User
}

// Option 2: Non-null assertion (use sparingly, only when you are certain)
function unsafeGetName(id: string): string {
  return getUser(id)!.name;
}

// Option 3: Optional chaining with nullish coalescing
function safeGetName(id: string): string {
  return getUser(id)?.name ?? "Anonymous";
}

noUncheckedIndexedAccess Pattern

// With noUncheckedIndexedAccess, array/record access returns T | undefined
const items: string[] = ["a", "b", "c"];
const first = items[0]; // string | undefined

// Guard before use
if (first !== undefined) {
  console.log(first.toUpperCase()); // OK
}

// Or use a helper
function at<T>(arr: T[], index: number): T {
  const item = arr[index];
  if (item === undefined) {
    throw new RangeError(`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
Type guards Runtime checks that narrow types for the compiler
Branded types Prevent mixing up primitives (UserId vs OrderId)
Builder pattern Enforce required steps at compile time
Zod schemas Runtime validation with automatic type inference
Declaration merging Extend third-party or global types
Strict mode Catch more bugs at compile time

References