# TypeScript Best Practices

> Idiomatic TypeScript patterns for clean, maintainable code.

- Skill: `ngxtm-devkit/typescript-best-practices` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add ngxtm-devkit/typescript-best-practices`
- Raw SKILL.md: https://api.skillmd.com/api/skills/ngxtm-devkit/typescript-best-practices/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: ngxtm (https://skillmd.com/u/ngxtm-devkit)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/ngxtm-devkit/typescript-best-practices

---


# TypeScript Best Practices

## **Priority: P1 (OPERATIONAL)**

Idiomatic patterns for writing clean, maintainable TypeScript code.

## Implementation Guidelines

- **Naming Conventions**:
  - PascalCase for classes, interfaces, types, enums
  - camelCase for variables, functions, methods, parameters
  - UPPER_SNAKE_CASE for constants
  - Prefix interfaces with `I` only when necessary for disambiguation
- **Functions**:
  - Prefer arrow functions for callbacks and short functions
  - Use regular functions for methods and exported functions
  - Always specify return types for public APIs
- **Modules**:
  - One export per file for major components/classes
  - Use named exports over default exports for better refactoring
  - Organize imports: external -> internal -> relative
- **Async/Await**:
  - Prefer `async/await` over raw Promises
  - Always handle errors with try/catch in async functions
  - Use `Promise.all()` for parallel operations
- **Classes**:
  - Use `private`/`protected`/`public` modifiers explicitly
  - Prefer composition over inheritance
  - Use `readonly` for properties that don't change after construction
- **Exhaustiveness Checking**: Use `never` type in `switch` cases.
- **Assertion Functions**: Use `asserts` for runtime type validation.
- **Optional Properties**: Use `?:`, not `| undefined`.
- **Type Imports**: Use `import type` for tree-shaking.

## Anti-Patterns

- **No Default Exports**: Use named exports.
- **No Implicit Returns**: Specify return types.
- **No Unused Variables**: Enable `noUnusedLocals`.
- **No `require`**: Use ES6 `import`.
- **No Empty Interfaces**: Use `type` or non-empty interface.

## Code

````typescript
// Named Export + Immutable Interface
export interface User {
  readonly id: string;
  name: string;
}

// Exhaustive Check
function getStatus(s: 'ok' | 'fail') {
  switch (s) {
    case 'ok': return 'OK';
    case 'fail': return 'Fail';
    default: const _chk: never = s; return _chk;
  }
}

// Assertion
function assertDefined<T>(val: T): asserts val is NonNullable<T> {
  if (val == null) throw new Error("Defined expected");
}
```  private readonly repository: UserRepository;

  constructor(repository: UserRepository) {
    this.repository = repository;
  }

  async getUser(id: string): Promise<UserProfile> {
    try {
      return await this.repository.findById(id);
    } catch (error) {
      throw new Error(`Failed to get user: ${error.message}`);
    }
  }
}

// Organize imports
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';

import { UserRepository } from '@/repositories/user.repository';
import { Logger } from '@/utils/logger';

// Type-only imports
import type { Request, Response } from 'express';
````

## Reference & Examples

For project structure and module organization:
See [references/REFERENCE.md](references/REFERENCE.md).

## Related Topics

language | tooling | security

