TypeScript Tips for Better Code

TypeScript has become the de facto standard for building large-scale JavaScript applications. Here are some practical tips to help you write better, more maintainable TypeScript code.

Use Type Inference Wisely

TypeScript’s type inference is powerful. Let the compiler infer types when possible, and only add explicit types when necessary:

// Good - let TypeScript infer the type
const users = ['Alice', 'Bob', 'Charlie'];

// Only add explicit types when needed
function processUsers(users: string[]): void {
  // ...
}

Leverage Union Types

Union types allow you to express values that can be one of several types:

type Status = 'loading' | 'success' | 'error';

function handleStatus(status: Status) {
  switch (status) {
    case 'loading':
      return 'Please wait...';
    case 'success':
      return 'Done!';
    case 'error':
      return 'Something went wrong';
  }
}

Use Type Guards

Type guards help TypeScript narrow down types in conditional blocks:

function isString(value: unknown): value is string {
  return typeof value === 'string';
}

function processValue(value: string | number) {
  if (isString(value)) {
    // TypeScript knows value is string here
    console.log(value.toUpperCase());
  } else {
    // TypeScript knows value is number here
    console.log(value.toFixed(2));
  }
}

Prefer Interfaces for Objects

Use interfaces for object shapes, especially when you might extend them:

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

interface Admin extends User {
  permissions: string[];
}

Use const Assertions

const assertions help preserve literal types:

// Without const assertion
const colors = ['red', 'green', 'blue']; // string[]

// With const assertion
const colors = ['red', 'green', 'blue'] as const; // readonly ["red", "green", "blue"]

Avoid any Type

The any type disables TypeScript’s type checking. Use unknown instead when you need to accept any value:

// Bad
function process(data: any) {
  return data.something;
}

// Good
function process(data: unknown) {
  if (typeof data === 'object' && data !== null && 'something' in data) {
    return (data as { something: string }).something;
  }
  throw new Error('Invalid data');
}

Use Utility Types

TypeScript provides powerful utility types that can save you time:

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

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

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

// Pick specific fields
type UserSummary = Pick<User, 'id' | 'name'>;

Conclusion

These tips will help you write more robust TypeScript code. Remember, the goal is to leverage TypeScript’s type system to catch errors early and make your code more maintainable.

Start applying these patterns in your projects and you’ll see the benefits immediately!