Skip to main content
โšก Calmops

TypeScript 2026 Complete Guide: Advanced Types, Patterns, and Best Practices

Introduction

TypeScript has become the standard for building scalable JavaScript applications. From startups to enterprises, TypeScript’s type safety and developer experience benefits have made it the default choice for serious JavaScript development.

This guide covers TypeScript in 2026, from foundational concepts to advanced patterns. Whether you’re starting your TypeScript journey or looking to level up, this guide provides practical insights for modern TypeScript development.

TypeScript Fundamentals

Why TypeScript?

TypeScript adds static typing to JavaScript, catching errors at compile time rather than runtime. This leads to:

  • Better IDE support: Intelligent autocomplete, refactoring, and inline documentation
  • Fewer runtime errors: Type checking catches many common mistakes
  • Easier maintenance: Types serve as documentation and enable safe refactoring
  • Improved collaboration: Explicit types make codebases easier to understand

Basic Types

// Primitive types
let name: string = "Alice";
let age: number = 30;
let isActive: boolean = true;

// Arrays
let numbers: number[] = [1, 2, 3];
let names: Array<string> = ["Alice", "Bob"];

// Objects
type User = {
  id: string;
  name: string;
  email: string;
  role: "admin" | "user" | "guest";
  createdAt: Date;
};

const user: User = {
  id: "usr_123",
  name: "Alice",
  email: "[email protected]",
  role: "admin",
  createdAt: new Date()
};

// Optional properties
type OptionalUser = {
  id: string;
  name: string;
  avatar?: string; // Optional
};

// Readonly
type ImmutableUser = {
  readonly id: string;
  readonly name: string;
};

Advanced Type System

Union and Intersection Types

// Union types - either/or
type StringOrNumber = string | number;
type Result = "success" | "error" | "loading";

// Type narrowing
function processValue(value: string | number) {
  if (typeof value === "string") {
    return value.toUpperCase(); // TypeScript knows it's string
  }
  return value.toFixed(2); // TypeScript knows it's number
}

// Discriminated unions
type Response<T> =
  | { status: "success"; data: T }
  | { status: "error"; error: Error }
  | { status: "loading" };

function handleResponse<T>(response: Response<T>) {
  switch (response.status) {
    case "success":
      return response.data; // T
    case "error":
      throw response.error;
    case "loading":
      return null;
  }
}

// Intersection types - combining types
type Admin = {
  permissions: string[];
};

type User = {
  name: string;
};

type AdminUser = User & Admin;

const admin: AdminUser = {
  name: "Alice",
  permissions: ["read", "write", "delete"]
};

Utility Types

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

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

// Readonly - all properties readonly
type ReadonlyUser = Readonly<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 of function
type ReturnOf = ReturnType<typeof fetch>;

// Parameters - get parameter types
type FetchParams = Parameters<typeof fetch>;

Template Literal Types

// Template literal types
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
type Endpoint = "/users" | "/posts" | "/comments";

type APIRoute = `${HttpMethod} ${Endpoint}`;

// Mapped types with template literals
type EventName = "click" | "focus" | "blur";
type Handler = `on${Capitalize<EventName>}`; // "onClick" | "onFocus" | "onBlur"

type Callbacks = {
  [K in Handler]: () => void;
};

// satisfies - validate types while keeping inference
const colors = {
  red: "#ff0000",
  green: "#00ff00",
  blue: "#0000ff"
} satisfies Record<string, string>;

Conditional Types

// Conditional types
type IsString<T> = T extends string ? true : false;

type A = IsString<string>; // true
type B = IsString<number>; // false

// Extract and Exclude
type T = Extract<"a" | "b" | "c", "a" | "f">; // "a"
type U = Exclude<"a" | "b" | "c", "a">; // "b" | "c"

// Awaited - unwrap Promises
type AwaitedResult = Awaited<Promise<Promise<number>>>; // number

// Inference with conditional types
type ReturnTypeOf<T> = T extends (...args: infer A) => infer R
  ? (args: A) => R
  : never;

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

// Using type guards
function processUnknown(value: unknown) {
  if (isString(value)) {
    return value.toUpperCase(); // TypeScript knows string
  }
  if (isUser(value)) {
    return value.name; // TypeScript knows User
  }
}

// Discriminated union guards
type Dog = { kind: "dog"; bark: () => void };
type Cat = { kind: "cat"; meow: () => void };
type Animal = Dog | Cat;

function handleAnimal(animal: Animal) {
  if (animal.kind === "dog") {
    animal.bark(); // TypeScript knows Dog
  } else {
    animal.meow(); // TypeScript knows Cat
  }
}

Generics Deep Dive

Generic Constraints

// Extending types
interface HasId {
  id: string;
}

function findById<T extends HasId>(items: T[], id: string): T | undefined {
  return items.find(item => item.id === id);
}

// Multiple constraints
function merge<T extends object, U extends object>(a: T, b: U): T & U {
  return { ...a, ...b };
}

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

Generic Defaults

// Default type parameters
interface ApiResponse<TData = unknown, TError = Error> {
  data?: TData;
  error?: TError;
  status: number;
}

// Using defaults
const response: ApiResponse<User> = {
  data: { id: "1", name: "Alice" },
  status: 200
};

const errorResponse: ApiResponse = {
  error: new Error("Failed"),
  status: 500
};

Mapped Types

// Mapped types
type Readonly<T> = {
  readonly [P in keyof T]: T[P];
};

type Nullable<T> = {
  [P in keyof T]: T[P] | null;
};

type Optional<T> = {
  [P in keyof T]?: T[P];
};

// Modify modifiers
type Writable<T> = {
  -readonly [P in keyof T]: T[P];
};

type Required<T> = {
  [P in keyof T]-?: T[P];
};

Design Patterns

Repository Pattern

// Generic repository
interface Repository<T> {
  findById(id: string): Promise<T | null>;
  findAll(): Promise<T[]>;
  create(item: Omit<T, "id">): Promise<T>;
  update(id: string, item: Partial<T>): Promise<T>;
  delete(id: string): Promise<void>;
}

class UserRepository implements Repository<User> {
  private collection = "users";
  
  async findById(id: string): Promise<User | null> {
    const doc = await db.collection(this.collection).doc(id).get();
    return doc.exists ? doc.data() as User : null;
  }
  
  async findAll(): Promise<User[]> {
    const snapshot = await db.collection(this.collection).get();
    return snapshot.docs.map(doc => doc.data() as User);
  }
  
  async create(item: Omit<User, "id">): Promise<User> {
    const doc = await db.collection(this.collection).add(item);
    return { id: doc.id, ...item };
  }
  
  async update(id: string, item: Partial<User>): Promise<User> {
    await db.collection(this.collection).doc(id).update(item);
    return (await this.findById(id))!;
  }
  
  async delete(id: string): Promise<void> {
    await db.collection(this.collection).doc(id).delete();
  }
}

Builder Pattern

// Type-safe builder
class QueryBuilder<T extends Record<string, unknown>> {
  private query: Partial<T> = {};
  
  where<K extends keyof T>(key: K, value: T[K]): this {
    this.query[key] = value;
    return this;
  }
  
  whereIn<K extends keyof T>(key: K, values: T[K][]): this {
    (this.query as any).$in = { [key]: values };
    return this;
  }
  
  orderBy<K extends keyof T>(key: K, direction: "asc" | "desc" = "asc"): this {
    (this.query as any).$order = { [key]: direction };
    return this;
  }
  
  limit(count: number): this {
    (this.query as any).$limit = count;
    return this;
  }
  
  build(): Partial<T> {
    return { ...this.query };
  }
}

// Usage
const query = new QueryBuilder<User>()
  .where("role", "admin")
  .whereIn("department", ["engineering", "sales"])
  .orderBy("name", "asc")
  .limit(10)
  .build();

Dependency Injection

// DI Container
type Constructor<T = any> = new (...args: any[]) => T;
type DependencyKey = string | symbol;

class Container {
  private dependencies = new Map<DependencyKey, any>();
  private singletons = new Map<DependencyKey, any>();
  
  register<T>(key: DependencyKey, factory: () => T): void {
    this.dependencies.set(key, factory);
  }
  
  registerSingleton<T>(key: DependencyKey, factory: () => T): void {
    this.dependencies.set(key, factory);
    this.singletons.set(key, null);
  }
  
  resolve<T>(key: DependencyKey): T {
    const factory = this.dependencies.get(key);
    if (!factory) {
      throw new Error(`Dependency ${String(key)} not registered`);
    }
    
    const isSingleton = this.singletons.has(key);
    if (isSingleton && this.singletons.get(key)) {
      return this.singletons.get(key);
    }
    
    const instance = factory();
    if (isSingleton) {
      this.singletons.set(key, instance);
    }
    
    return instance;
  }
}

// Decorator-based DI
function injectable(target: any, context: ClassDecoratorContext) {
  // Store metadata for later registration
  return target;
}

function inject(key: string) {
  return function (target: any, propertyKey: string | symbol) {
    // Inject dependency
  };
}

Error Handling

Result Type Pattern

// Result type for explicit error handling
type Result<T, E = Error> =
  | { ok: true; value: T }
  | { ok: false; error: E };

async function fetchUser(id: string): Promise<Result<User>> {
  try {
    const user = await db.users.findById(id);
    if (!user) {
      return { ok: false, error: new Error("User not found") };
    }
    return { ok: true, value: user };
  } catch (error) {
    return { ok: false, error: error as Error };
  }
}

// Usage
const result = await fetchUser("123");
if (result.ok) {
  console.log(result.value.name);
} else {
  console.error(result.error.message);
}

Zod Integration

import { z } from "zod";

// Define schemas
const UserSchema = z.object({
  id: z.string().uuid(),
  name: z.string().min(1).max(100),
  email: z.string().email(),
  role: z.enum(["admin", "user", "guest"]),
  createdAt: z.coerce.date()
});

type User = z.infer<typeof UserSchema>;

// Validate runtime data
function createUser(data: unknown): Result<User, z.ZodError> {
  try {
    const valid = UserSchema.parse(data);
    return { ok: true, value: valid };
  } catch (error) {
    return { ok: false, error };
  }
}

// API validation
const createUserSchema = z.object({
  name: z.string().min(1),
  email: z.string().email(),
  password: z.string().min(8)
});

app.post("/users", async (req, res) => {
  const result = createUserSchema.safeParse(req.body);
  if (!result.success) {
    return res.status(400).json(result.error);
  }
  
  const user = await createUser(result.data);
  res.json(user);
});

Performance Optimization

Type Inference Optimization

// Avoid unnecessary type annotations - let TypeScript infer
// Good
const users = await db.users.findMany();
const activeUsers = users.filter(u => u.isActive);

// Less necessary
const users: User[] = await db.users.findMany();
const activeUsers: User[] = users.filter((u: User) => u.isActive);

// Use const assertions for literal types
const routes = {
  home: "/",
  about: "/about",
  contact: "/contact"
} as const;

type Route = typeof routes[keyof typeof routes];

Bundle Optimization

// Tree-shaking friendly exports
// Good - named exports
export function useState<T>(initial: T) { /* ... */ }
export function useEffect(fn: () => void) { /* ... */ }

// Avoid - barrel files with side effects
// bad-export.ts
export * from "./users"; // May include unused code

// Better - explicit exports
export { UserCard } from "./users";
export { UserList } from "./users";
export { UserForm } from "./users";

// Conditional exports for different environments
// package.json
{
  "exports": {
    ".": "./dist/index.js",
    "./server": "./dist/server.js",
    "./client": "./dist/client.js"
  }
}

TypeScript Configuration

tsconfig.json Best Practices

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "lib": ["ES2022", "DOM", "DOM.Iterable"],
    
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "exactOptionalPropertyTypes": true,
    
    "moduleResolution": "bundler",
    "esModuleInterop": true,
    "isolatedModules": true,
    "verbatimModuleSyntax": true,
    
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,
    
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

Project References

// Main tsconfig.json
{
  "files": [],
  "references": [
    { "path": "./packages/common" },
    { "path": "./packages/client" },
    { "path": "./packages/server" }
  ]
}

// packages/common/tsconfig.json
{
  "compilerOptions": {
    "composite": true,
    "outDir": "./dist",
    "rootDir": "./src"
  }
}

External Resources

Official Documentation

Learning Resources

Tools

  • ts-node - Run TypeScript directly
  • tsx - Fast TypeScript runner
  • volta - TypeScript version manager

Conclusion

TypeScript in 2026 offers a powerful type system that enables building robust, maintainable applications. The key to TypeScript mastery is understanding when to use advanced features and when simpler solutions suffice.

Start with strict mode and basic types. As your codebase grows, leverage utility types, generics, and design patterns to maintain type safety. Use tools like Zod for runtime validation where needed. Keep your TypeScript version current to benefit from the latest language features.

The investment in TypeScript pays dividends through better developer experience, fewer bugs, and easier code maintenance. Embrace it fully, and your projects will thank you.

Comments