Featured
10 min read

TypeScript Best Practices for 2024

Essential TypeScript best practices to write cleaner, more maintainable code with better type safety.

Zain Shaikh
January 1, 2024
10 min read
typescript
best practices
programming
type safety

TypeScript Best Practices for 2024

TypeScript has become the standard for large-scale JavaScript applications. Here are the essential best practices to write better TypeScript code.

1. Use Strict Mode

Always enable strict mode in your tsconfig.json:

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

2. Prefer Interfaces Over Types

Use interfaces for object shapes:

interface User {
  id: number;
  name: string;
  email: string;
  isActive: boolean;
}

// Instead of:
type User = {
  id: number;
  name: string;
  email: string;
  isActive: boolean;
}

3. Use Union Types for State

Represent state with union types:

type LoadingState = 'idle' | 'loading' | 'success' | 'error';

interface ApiState<T> {
  data: T | null;
  state: LoadingState;
  error: string | null;
}

4. Leverage Generic Constraints

Use generic constraints for better type safety:

interface HasId {
  id: number;
}

function updateItem<T extends HasId>(item: T, updates: Partial<T>): T {
  return { ...item, ...updates };
}

5. Use Discriminated Unions

For complex state management:

type ApiResponse<T> = 
  | { status: 'loading' }
  | { status: 'success'; data: T }
  | { status: 'error'; error: string };

function handleResponse<T>(response: ApiResponse<T>) {
  switch (response.status) {
    case 'loading':
      return 'Loading...';
    case 'success':
      return response.data;
    case 'error':
      return response.error;
  }
}

6. Prefer Readonly Arrays

Use readonly arrays when you don't need to mutate:

function processItems(items: readonly string[]): string[] {
  return items.map(item => item.toUpperCase());
}

7. Use Utility Types

Leverage built-in utility types:

interface User {
  id: number;
  name: string;
  email: string;
  password: string;
}

// Create a type without password
type PublicUser = Omit<User, 'password'>;

// Make all properties optional
type PartialUser = Partial<User>;

// Pick specific properties
type UserName = Pick<User, 'name' | 'email'>;

8. Use Const Assertions

For literal types:

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

9. Prefer Function Declarations

Use function declarations for better hoisting:

function calculateTotal(items: Item[]): number {
  return items.reduce((sum, item) => sum + item.price, 0);
}

10. Use Proper Error Handling

Create custom error types:

class ValidationError extends Error {
  constructor(message: string, public field: string) {
    super(message);
    this.name = 'ValidationError';
  }
}

function validateUser(user: unknown): User {
  if (!user || typeof user !== 'object') {
    throw new ValidationError('Invalid user data', 'user');
  }
  
  // ... validation logic
}

11. Use Template Literal Types

For string manipulation:

type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
type ApiEndpoint = `/api/${string}`;

type ApiCall = {
  method: HttpMethod;
  endpoint: ApiEndpoint;
};

12. Prefer Composition Over Inheritance

Use composition patterns:

interface Logger {
  log(message: string): void;
}

interface Database {
  save(data: unknown): Promise<void>;
}

class UserService {
  constructor(
    private logger: Logger,
    private database: Database
  ) {}
  
  async createUser(user: User): Promise<void> {
    this.logger.log('Creating user');
    await this.database.save(user);
  }
}

Conclusion

Following these best practices will help you write more maintainable, type-safe TypeScript code. Remember to:

  • Use strict mode
  • Prefer interfaces over types
  • Leverage union types and discriminated unions
  • Use utility types effectively
  • Write proper error handling
  • Choose composition over inheritance

These practices will make your code more robust and easier to maintain!