Skip to content
All essays
WebMarch 25, 202415 min

TypeScript Advanced Types Guide

Master advanced TypeScript features: generics, conditional types, mapped types, utility types, and type manipulations

Ü
Ümit Uz
Mobile & Full Stack Developer

TypeScript's type system is incredibly powerful and expressive. Let's dive deep into advanced type features that will make your code type-safe and maintainable.

Generics Deep Dive

Basic Generics

Generics allow you to create reusable components that work with a variety of types.

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

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

Generic Constraints

Constrain generics to specific shapes:

typescript
interface Lengthwise {
  length: number;
}

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

logLength("hello"); // Works
logLength([1, 2, 3]); // Works
logLength(42); // Error

Multiple Type Parameters

typescript
function pair<T, U>(first: T, second: U): [T, U] {
  return [first, second];
}

const p = pair(1, "hello");

Conditional Types

Conditional types allow you to choose types based on conditions.

typescript
type NonNullable<T> = T extends null | undefined ? never : T;

type Result = NonNullable<string | null>; // string

infer Keyword

Extract type from another type:

typescript
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;

type AsyncData = Promise<string>;
type Data = UnwrapPromise<AsyncData>; // string

Mapped Types

Transform properties of an existing type:

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

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

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

type ReadonlyUser = Readonly<User>;
type PartialUser = Partial<User>;

Utility Types

TypeScript Built-in Utilities

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

// Omit - Remove specific properties
type CreateUser = Omit<User, 'id'>;

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

// Exclude - Remove types from union
type Primitives = Exclude<string | number | boolean, string>;

// Extract - Extract types from union
type Strings = Extract<string | number | boolean, string>;

// ReturnType - Get return type of function
type FnReturn = ReturnType<() => string>;

// Parameters - Get parameter types of function
type FnParams = Parameters<(a: string, b: number) => void>;

Template Literal Types

Create string types from patterns:

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

type ClickEvent = EventName<"click">; // "clickChanged"

type CssProperty = `margin${"top" | "bottom" | "left" | "right"}`;
// "marginTop" | "marginBottom" | "marginLeft" | "marginRight"

Branded Types

Add brand to primitive types for type safety:

typescript
type USD = number & { readonly __brand: unique symbol };
type EUR = number & { readonly __brand: unique symbol };

const usd = (amount: number): USD = amount as USD;
const eur = (amount: number): EUR = amount as EUR;

const addFunds = (wallet: USD, amount: USD): USD => {
  return usd(wallet + amount);
};

let wallet = usd(100);
wallet = addFunds(wallet, usd(50));
// wallet = addFunds(wallet, eur(50)); // Error

Type Guards and Discriminators

typeof Type Guard

typescript
function process(value: string | number) {
  if (typeof value === "string") {
    return value.toUpperCase();
  }
  return value * 2;
}

instanceof Type Guard

typescript
class Dog {
  bark() { console.log("Woof!"); }
}

class Cat {
  meow() { console.log("Meow!"); }
}

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

Discriminated Unions

typescript
interface Success<T> {
  type: "success";
  data: T;
}

interface Error {
  type: "error";
  message: string;
}

type Result<T> = Success<T> | Error;

function handleResult<T>(result: Result<T>) {
  if (result.type === "success") {
    console.log(result.data);
  } else {
    console.log(result.message);
  }
}

Advanced Pattern Matching

typescript
type Match = {
  pattern: RegExp;
  handler: (match: RegExpMatchArray) => void;
};

function route(path: string, routes: Match[]) {
  for (const route of routes) {
    const match = path.match(route.pattern);
    if (match) {
      route.handler(match);
      return;
    }
  }
}

Conclusion

TypeScript's advanced type system enables you to catch errors at compile time, improve IDE support, and make your code more maintainable. Master generics, conditional types, and utility types to write truly type-safe code.

Related essays

Next essay
Webpack Complete Guide