typescript-best-practices

Apply TypeScript best practices when writing or reviewing TypeScript code, including strict typing, generics, error handling, and common patterns.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "typescript-best-practices" with this command: npx skills add codelab614/agents-skills/codelab614-agents-skills-typescript-best-practices

TypeScript Best Practices

When to Apply

Apply these rules when:

  • Writing new TypeScript code
  • Reviewing TypeScript code
  • Refactoring JavaScript to TypeScript
  • Fixing type errors
  • Optimizing type definitions

Core Rules

1. Avoid any - Use Proper Types

Never use any unless absolutely necessary. Instead:

  • Use unknown for truly unknown types
  • Use generics for flexible typing
  • Use union types for multiple possibilities
// BAD
function parse(data: any): any { ... }

// GOOD
function parse<T>(data: unknown): T | null { ... }

2. Enable Strict Mode

Always use strict TypeScript configuration:

{
  "compilerOptions": {
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "noImplicitReturns": true
  }
}

3. Use Type Guards for Runtime Checks

// Type guard function
function isUser(value: unknown): value is User {
  return (
    typeof value === 'object' &&
    value !== null &&
    'id' in value &&
    'name' in value
  );
}

// Usage
if (isUser(data)) {
  console.log(data.name); // TypeScript knows it's User
}

4. Prefer Interfaces for Objects, Types for Unions

// Use interface for object shapes
interface User {
  id: string;
  name: string;
  email: string;
}

// Use type for unions and complex types
type Status = 'pending' | 'active' | 'inactive';
type Result<T> = { success: true; data: T } | { success: false; error: Error };

5. Use Readonly for Immutable Data

interface Config {
  readonly apiUrl: string;
  readonly timeout: number;
}

// Or use Readonly utility
type ImmutableUser = Readonly<User>;

6. Leverage Utility Types

// Partial - all properties optional
type UserUpdate = Partial<User>;

// Pick - select specific properties
type UserPreview = Pick<User, 'id' | 'name'>;

// Omit - exclude properties
type UserWithoutEmail = Omit<User, 'email'>;

// Record - typed object maps
type UserRoles = Record<string, 'admin' | 'user' | 'guest'>;

7. Type-Safe Error Handling

// Define error types
class AppError extends Error {
  constructor(
    message: string,
    public code: string,
    public statusCode: number
  ) {
    super(message);
    this.name = 'AppError';
  }
}

// Result pattern
type Result<T, E = Error> =
  | { ok: true; value: T }
  | { ok: false; error: E };

function divide(a: number, b: number): Result<number> {
  if (b === 0) {
    return { ok: false, error: new Error('Division by zero') };
  }
  return { ok: true, value: a / b };
}

8. Use Generics for Reusable Code

// Generic function
function first<T>(array: T[]): T | undefined {
  return array[0];
}

// Generic with constraints
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

// Generic interface
interface Repository<T> {
  find(id: string): Promise<T | null>;
  save(entity: T): Promise<T>;
  delete(id: string): Promise<boolean>;
}

9. Const Assertions for Literal Types

// Without as const - type is string[]
const colors = ['red', 'green', 'blue'];

// With as const - type is readonly ['red', 'green', 'blue']
const colors = ['red', 'green', 'blue'] as const;
type Color = typeof colors[number]; // 'red' | 'green' | 'blue'

10. Discriminated Unions for State

type AsyncState<T> =
  | { status: 'idle' }
  | { status: 'loading' }
  | { status: 'success'; data: T }
  | { status: 'error'; error: Error };

function render<T>(state: AsyncState<T>) {
  switch (state.status) {
    case 'idle':
      return 'Ready';
    case 'loading':
      return 'Loading...';
    case 'success':
      return `Data: ${state.data}`; // TypeScript knows data exists
    case 'error':
      return `Error: ${state.error.message}`; // TypeScript knows error exists
  }
}

Naming Conventions

TypeConventionExample
InterfacePascalCaseUserProfile
Type aliasPascalCaseRequestConfig
EnumPascalCaseHttpStatus
Enum memberUPPER_SNAKE or PascalCaseHTTP_OK or HttpOk
GenericSingle uppercase letter or descriptiveT, TData, TResponse
FunctioncamelCasegetUserById
VariablecamelCaseuserList
ConstantUPPER_SNAKE or camelCaseMAX_RETRIES or maxRetries

Anti-Patterns to Avoid

  1. Don't use any as escape hatch - Use unknown and narrow the type
  2. Don't use ! non-null assertion - Check for null/undefined properly
  3. Don't use @ts-ignore - Fix the type error instead
  4. Don't export everything - Only export what's needed
  5. Don't use Function type - Use specific function signatures

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

typescript-best-practices

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

typescript-best-practices

No summary provided by upstream source.

Repository SourceNeeds Review
162-jwynia
Coding

typescript-best-practices

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

busirocket-typescript-standards

No summary provided by upstream source.

Repository SourceNeeds Review