Back to blog
TypeScript Best Practices
TypeScript

TypeScript Best Practices

Discover the latest TypeScript patterns and practices that will help you write more maintainable and type-safe code.

Use Strict Mode

Always enable strict mode in your tsconfig.json:

{
  "compilerOptions": {
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "noImplicitOverride": true
  }
}

This catches many common errors at compile time rather than runtime.

Prefer Type Inference

Let TypeScript infer types when possible:

// Good
const user = { name: "John", age: 30 }

// Unnecessary
const user: { name: string; age: number } = { name: "John", age: 30 }

Use Union Types Over Enums

Union types are more flexible and tree-shakeable:

// Prefer this
type Status = "pending" | "approved" | "rejected"

// Over this
enum Status {
  Pending = "pending",
  Approved = "approved",
  Rejected = "rejected"
}

Leverage Type Guards

Type guards help TypeScript narrow types:

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())
  }
}

Use unknown Instead of any

The unknown type is safer than any:

// Avoid
function process(data: any) {
  return data.value // No type checking
}

// Better
function process(data: unknown) {
  if (typeof data === "object" && data !== null && "value" in data) {
    return data.value
  }
}

Utility Types Are Your Friend

TypeScript provides powerful utility types:

  • Partial<T>: Make all properties optional
  • Required<T>: Make all properties required
  • Pick<T, K>: Select specific properties
  • Omit<T, K>: Exclude specific properties
  • Record<K, T>: Create an object type with specific keys
interface User {
  id: string
  name: string
  email: string
  age: number
}

type UserPreview = Pick<User, "id" | "name">
type UserUpdate = Partial<Omit<User, "id">>
MoebiusSam Fortin