typescript-refactoring-patterns

autohandai's avatarfrom autohandai

Expert TypeScript refactoring patterns for cleaner, type-safe code

⭐0stars🔀0forks📁View on GitHub🕐Updated Jan 2, 2026

When & Why to Use This Skill

This Claude skill provides expert-level TypeScript refactoring capabilities, enabling developers to automatically transform legacy or loosely typed code into clean, type-safe, and maintainable structures. By leveraging advanced patterns like discriminated unions, strategy patterns, and branded types, it helps eliminate technical debt and ensures robust type safety across complex codebases.

Use Cases

  • Refactoring complex interfaces with multiple boolean flags into type-safe discriminated unions to prevent invalid states and ensure exhaustive checking.
  • Replacing rigid switch-case statements with polymorphic strategy patterns to improve code modularity and extensibility.
  • Implementing branded types for domain-specific identifiers (e.g., UserId, Email) to prevent primitive obsession and accidental type mixing.
  • Systematically removing 'any' types and non-null assertions by introducing proper type guards and 'unknown' type validation for better runtime safety.
  • Modernizing TypeScript configurations and adopting 'satisfies' or 'as const' patterns to optimize type inference and literal handling.
nametypescript-refactoring-patterns
descriptionExpert TypeScript refactoring patterns for cleaner, type-safe code
licenseMIT
compatibilitytypescript 5.0+
allowed-toolsread_file write_file apply_patch search_with_context

TypeScript Refactoring Patterns

Core Principles

  1. Type Narrowing Over Type Assertions - Use type guards and discriminated unions instead of as casts
  2. Const Assertions for Literals - Use as const for immutable literal types
  3. Generic Constraints - Prefer extends constraints over any
  4. Branded Types - Use branded types for domain-specific validation

Refactoring Patterns

Extract Discriminated Union

When you see multiple boolean flags, refactor to discriminated union:

// Before
interface User {
  isAdmin: boolean;
  isGuest: boolean;
  permissions?: string[];
}

// After
type User =
  | { role: 'admin'; permissions: string[] }
  | { role: 'guest' }
  | { role: 'member'; permissions: string[] };

Replace Conditional with Polymorphism

When you see switch statements on type, use the strategy pattern:

// Before
function process(item: Item) {
  switch (item.type) {
    case 'a': return processA(item);
    case 'b': return processB(item);
  }
}

// After
const processors: Record<ItemType, (item: Item) => Result> = {
  a: processA,
  b: processB,
};
const process = (item: Item) => processors[item.type](item);

Extract Type Guard

When narrowing types, create reusable type guards:

function isNonNullable<T>(value: T): value is NonNullable<T> {
  return value !== null && value !== undefined;
}

// Usage
const items = array.filter(isNonNullable);

Use Branded Types for Validation

Prevent primitive obsession with branded types:

type UserId = string & { readonly brand: unique symbol };
type Email = string & { readonly brand: unique symbol };

function createUserId(id: string): UserId {
  if (!isValidUuid(id)) throw new Error('Invalid user ID');
  return id as UserId;
}

Code Smell Detectors

Watch for these patterns and refactor:

  • any types (replace with unknown + type guards)
  • Non-null assertions ! (add proper checks)
  • Type assertions as (use type guards)
  • Optional chaining abuse ?.?.?. (restructure data)
  • Index signatures without validation

Quick Wins

  1. Enable strict: true in tsconfig
  2. Use satisfies for type checking without widening
  3. Prefer readonly arrays and objects
  4. Use unknown for external data, validate at boundaries