JavaScript 8 min read

TypeScript Advanced Guide: Types, Generics, and Patterns

Master advanced TypeScript concepts. Learn generics, utility types, conditional types, mapped types, and professional patterns for type-safe code.

MR

Moshiour Rahman

Advertisement

Why Advanced TypeScript?

TypeScript’s type system is incredibly powerful. Mastering advanced features enables better code organization, fewer bugs, and improved developer experience.

Setup

npm install -g typescript
tsc --init

Advanced Types

Union and Intersection Types

// Union types (OR)
type Status = "pending" | "approved" | "rejected";
type ID = string | number;

function printId(id: ID) {
  if (typeof id === "string") {
    console.log(id.toUpperCase());
  } else {
    console.log(id);
  }
}

// Intersection types (AND)
type Person = { name: string; age: number };
type Employee = { employeeId: string; department: string };

type Staff = Person & Employee;

const staff: Staff = {
  name: "John",
  age: 30,
  employeeId: "E123",
  department: "Engineering"
};

Literal Types

// String literals
type Direction = "north" | "south" | "east" | "west";

// Numeric literals
type DiceRoll = 1 | 2 | 3 | 4 | 5 | 6;

// Template literal types
type EventName = `on${Capitalize<string>}`;
type CSSProperty = `${string}-${string}`;

// Const assertions
const config = {
  endpoint: "/api",
  method: "GET"
} as const;
// type: { readonly endpoint: "/api"; readonly method: "GET" }

Type Guards

// typeof guard
function padLeft(value: string, padding: string | number) {
  if (typeof padding === "number") {
    return " ".repeat(padding) + value;
  }
  return padding + value;
}

// instanceof guard
class Dog { bark() {} }
class Cat { meow() {} }

function speak(animal: Dog | Cat) {
  if (animal instanceof Dog) {
    animal.bark();
  } else {
    animal.meow();
  }
}

// Custom type guard
interface Fish { swim(): void }
interface Bird { fly(): void }

function isFish(pet: Fish | Bird): pet is Fish {
  return (pet as Fish).swim !== undefined;
}

// Discriminated unions
type Shape =
  | { kind: "circle"; radius: number }
  | { kind: "rectangle"; width: number; height: number };

function getArea(shape: Shape): number {
  switch (shape.kind) {
    case "circle":
      return Math.PI * shape.radius ** 2;
    case "rectangle":
      return shape.width * shape.height;
  }
}

Generics

Basic Generics

// Generic function
function identity<T>(arg: T): T {
  return arg;
}

const num = identity<number>(42);
const str = identity("hello"); // Type inferred

// Generic interface
interface Box<T> {
  value: T;
}

const numberBox: Box<number> = { value: 42 };
const stringBox: Box<string> = { value: "hello" };

// Generic class
class Stack<T> {
  private items: T[] = [];

  push(item: T): void {
    this.items.push(item);
  }

  pop(): T | undefined {
    return this.items.pop();
  }
}

const stack = new Stack<number>();
stack.push(1);
stack.push(2);

Generic Constraints

// Constrain to types with length property
interface Lengthwise {
  length: number;
}

function logLength<T extends Lengthwise>(arg: T): T {
  console.log(arg.length);
  return arg;
}

logLength("hello");     // OK
logLength([1, 2, 3]);   // OK
// logLength(123);      // Error: number has no length

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

const person = { name: "John", age: 30 };
const name = getProperty(person, "name"); // string
// getProperty(person, "invalid");  // Error

Multiple Type Parameters

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

const numbers = [1, 2, 3];
const strings = map(numbers, n => n.toString());

// Generic with defaults
interface Response<T = any> {
  data: T;
  status: number;
}

const response: Response = { data: "anything", status: 200 };
const typedResponse: Response<User> = { data: user, status: 200 };

Utility Types

Built-in Utility Types

interface User {
  id: number;
  name: string;
  email: string;
  age?: number;
}

// 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 UserCredentials = Pick<User, "email" | "name">;

// Omit - exclude specific properties
type UserWithoutId = Omit<User, "id">;

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

// Exclude - remove from union
type Status = "pending" | "approved" | "rejected";
type ActiveStatus = Exclude<Status, "rejected">;

// Extract - keep from union
type PendingStatus = Extract<Status, "pending">;

// NonNullable - remove null and undefined
type Name = string | null | undefined;
type NonNullName = NonNullable<Name>; // string

// ReturnType - get function return type
function getUser() {
  return { id: 1, name: "John" };
}
type UserType = ReturnType<typeof getUser>;

// Parameters - get function parameters
type GetUserParams = Parameters<typeof getUser>;

Custom Utility Types

// Make specific properties optional
type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;

type UserWithOptionalEmail = PartialBy<User, "email">;

// Make specific properties required
type RequiredBy<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>;

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

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

// Mutable (remove readonly)
type Mutable<T> = {
  -readonly [P in keyof T]: T[P];
};

Conditional Types

Basic Conditional Types

// T extends U ? X : Y
type IsString<T> = T extends string ? true : false;

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

// Extract array element type
type ArrayElement<T> = T extends (infer E)[] ? E : T;

type Elem = ArrayElement<string[]>;  // string
type NotArray = ArrayElement<number>;  // number

// Get promise value type
type Awaited<T> = T extends Promise<infer U> ? U : T;

type PromiseValue = Awaited<Promise<string>>;  // string

Distributive Conditional Types

type ToArray<T> = T extends any ? T[] : never;

type StringOrNumberArray = ToArray<string | number>;
// string[] | number[]

// Prevent distribution with tuple
type ToArrayNonDist<T> = [T] extends [any] ? T[] : never;

type SingleArray = ToArrayNonDist<string | number>;
// (string | number)[]

Mapped Types

Basic Mapped Types

// Make all properties optional
type MyPartial<T> = {
  [P in keyof T]?: T[P];
};

// Make all properties required
type MyRequired<T> = {
  [P in keyof T]-?: T[P];
};

// Make all properties readonly
type MyReadonly<T> = {
  readonly [P in keyof T]: T[P];
};

// Transform property names
type Getters<T> = {
  [P in keyof T as `get${Capitalize<string & P>}`]: () => T[P];
};

interface Person {
  name: string;
  age: number;
}

type PersonGetters = Getters<Person>;
// { getName: () => string; getAge: () => number }

Template Literal Types

type EventName<T extends string> = `${T}Changed`;

type UserEvents = EventName<"name" | "age">;
// "nameChanged" | "ageChanged"

// Create event handlers
type EventHandlers<T> = {
  [K in keyof T as `on${Capitalize<string & K>}Change`]: (value: T[K]) => void;
};

interface Config {
  theme: string;
  language: string;
}

type ConfigHandlers = EventHandlers<Config>;
// {
//   onThemeChange: (value: string) => void;
//   onLanguageChange: (value: string) => void;
// }

Advanced Patterns

Builder Pattern

class QueryBuilder<T extends object = {}> {
  private query: T;

  constructor(query: T = {} as T) {
    this.query = query;
  }

  select<K extends string>(field: K): QueryBuilder<T & { select: K }> {
    return new QueryBuilder({ ...this.query, select: field });
  }

  where<K extends string, V>(
    field: K,
    value: V
  ): QueryBuilder<T & { where: { field: K; value: V } }> {
    return new QueryBuilder({
      ...this.query,
      where: { field, value }
    });
  }

  build(): T {
    return this.query;
  }
}

const query = new QueryBuilder()
  .select("name")
  .where("age", 30)
  .build();

Factory Pattern

interface Product {
  name: string;
  price: number;
}

interface ProductFactory<T extends Product> {
  create(data: Omit<T, "id">): T & { id: string };
}

function createFactory<T extends Product>(): ProductFactory<T> {
  return {
    create(data) {
      return {
        ...data,
        id: Math.random().toString(36).substr(2, 9)
      } as T & { id: string };
    }
  };
}

interface Book extends Product {
  author: string;
}

const bookFactory = createFactory<Book>();
const book = bookFactory.create({
  name: "TypeScript Guide",
  price: 29.99,
  author: "John Doe"
});

Type-Safe Event Emitter

type EventMap = {
  userLogin: { userId: string; timestamp: Date };
  userLogout: { userId: string };
  error: { message: string; code: number };
};

class TypedEventEmitter<T extends Record<string, any>> {
  private listeners: { [K in keyof T]?: ((data: T[K]) => void)[] } = {};

  on<K extends keyof T>(event: K, callback: (data: T[K]) => void): void {
    if (!this.listeners[event]) {
      this.listeners[event] = [];
    }
    this.listeners[event]!.push(callback);
  }

  emit<K extends keyof T>(event: K, data: T[K]): void {
    this.listeners[event]?.forEach(callback => callback(data));
  }
}

const emitter = new TypedEventEmitter<EventMap>();

emitter.on("userLogin", (data) => {
  console.log(data.userId, data.timestamp);
});

emitter.emit("userLogin", {
  userId: "123",
  timestamp: new Date()
});

Summary

ConceptUse Case
GenericsReusable type-safe functions/classes
Utility TypesTransform existing types
Conditional TypesType logic and inference
Mapped TypesTransform object types
Template LiteralsString manipulation at type level

Advanced TypeScript enables building robust, type-safe applications with excellent developer experience.

Advertisement

MR

Moshiour Rahman

Software Architect & AI Engineer

Share:
MR

Moshiour Rahman

Software Architect & AI Engineer

Enterprise software architect with deep expertise in financial systems, distributed architecture, and AI-powered applications. Building large-scale systems at Fortune 500 companies. Specializing in LLM orchestration, multi-agent systems, and cloud-native solutions. I share battle-tested patterns from real enterprise projects.

Related Articles

Comments

Comments are powered by GitHub Discussions.

Configure Giscus at giscus.app to enable comments.