Skip to main content

TypeScript Best Practices 2026: Modern TypeScript Development

Created: March 7, 2026 CalmOps 3 min read

Introduction

TypeScript has become essential for professional web development. Understanding best practices and advanced patterns helps you write safer, more maintainable code.

This guide covers modern TypeScript best practices for 2026.

Type Inference

Let TypeScript Infer

// Let inference work
const name = "John";        // type: "John"
const count = 10;           // type: number
const items = [];           // type: never[] - add type!
const items = [] as const;  // type: readonly []

// Explicit when needed
const config: Config = loadConfig();

Type Annotations

// Function parameters need annotations
function greet(name: string): string {
  return `Hello, ${name}`;
}

// Complex object types
interface User {
  id: string;
  name: string;
  email: string;
  role: 'admin' | 'user' | 'guest';
}

Utility Types

Common Utilities

// Partial - make all optional
type PartialUser = Partial<User>;

// Required - make all required
type RequiredUser = Required<User>;

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

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

// Record - create object types
type UserMap = Record<string, User>;

// ReturnType - get return type
type ApiResponse = ReturnType<typeof fetch> extends Promise<infer T> ? T : never;

Custom Utility Types

// Nullable
type Nullable<T> = T | null;

// DeepPartial
type DeepPartial<T> = {
  [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};

// Branded type for type safety
type Branded<T, B> = T & { __brand: B };
type UserId = Branded<string, 'UserId'>;

Generics

Generic Functions

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

function map<T, U>(arr: T[], fn: (item: T) => U): U[] {
  return arr.map(fn);
}

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

Generic Classes

class Container<T> {
  private value: T;
  
  constructor(value: T) {
    this.value = value;
  }
  
  get(): T {
    return this.value;
  }
  
  set(value: T): void {
    this.value = value;
  }
}

Error Handling

Result Type

type Result<T, E = Error> =
  | { success: true; data: T }
  | { success: false; error: E };

async function fetchUser(id: string): Promise<Result<User>> {
  try {
    const response = await fetch(`/api/users/${id}`);
    if (!response.ok) {
      return { success: false, error: new Error('User not found') };
    }
    return { success: true, data: await response.json() };
  } catch (error) {
    return { success: false, error: error as Error };
  }
}

Type Guards

Custom Type Guards

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

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

// Usage
if (isUser(data)) {
  console.log(data.name);
}

Strict Mode

Configuration

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

Conclusion

TypeScript best practices help you write safer code. Use inference wisely, leverage utility types, and enable strict mode.

Resources

Comments

Share this article

Scan to read on mobile