Explore more sophisticated type patterns and techniques.

Index Signatures

Define the structure of objects with dynamic keys.

// String index signature
interface StringDictionary {
  [key: string]: string;
}

const dict: StringDictionary = {
  hello: "world",
  foo: "bar"
};

// Number index signature
interface NumberArray {
  [index: number]: string;
}

const arr: NumberArray = ["a", "b", "c"];

Combining Index Signatures

interface MixedDictionary {
  [key: string]: string | number;
  [key: number]: string;  // Must be compatible with string index
}

keyof Operator

Get a union of all keys of a type.

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

type UserKeys = keyof User;
// "name" | "age" | "email"

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

const user: User = { name: "Alice", age: 25, email: "alice@example.com" };
getProperty(user, "name");  // string

typeof Operator

Get the type of a value.

const user = {
  name: "Alice",
  age: 25
};

type User = typeof user;
// { name: string; age: number; }

// With functions
function createUser() {
  return { name: "Alice", age: 25 };
}

type User = ReturnType<typeof createUser>;

Template Literal Types

Create types from string templates (TypeScript 4.1+).

type EventName<T extends string> = `on${Capitalize<T>}`;
type ClickEvent = EventName<"click">;
// "onClick"

type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
type ApiEndpoint = `/${string}`;
type ApiRoute = `${HttpMethod} ${ApiEndpoint}`;

type Route = "GET /users" | "POST /users" | "GET /posts";
// Type-safe API routes

Lookup Types

Access property types using bracket notation.

interface User {
  name: string;
  address: {
    street: string;
    city: string;
  };
}

type UserName = User["name"];
// string

type Address = User["address"];
// { street: string; city: string; }

type Street = User["address"]["street"];
// string

Mapped Types (Introduction)

Transform types by mapping over properties.

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

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

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

type ReadonlyUser = Readonly<User>;
// { readonly name: string; readonly age: number; }

Conditional Types (Introduction)

Types that depend on conditions.

type IsArray<T> = T extends Array<any> ? true : false;

type Test1 = IsArray<string[]>;  // true
type Test2 = IsArray<string>;   // false

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

Distributive Conditional Types

Conditional types distribute over union types.

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

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

// To prevent distribution, use brackets
type ToArrayNonDist<T> = [T] extends [any] ? T[] : never;
type StrOrNumArr = ToArrayNonDist<string | number>;
// (string | number)[]

Exercises

Exercise 1: Index Signatures

// Create an interface for a cache with:
// - String keys
// - Values can be any type
// - A method to get/set values

Exercise 2: keyof and Lookup Types

// Create a function 'getNestedProperty' that:
// - Takes an object and a path (array of keys)
// - Returns the value at that path
// - Use keyof and lookup types for type safety

Exercise 3: Template Literal Types

// Create a type for CSS class names:
// - Prefix: "btn-"
// - Suffix: "primary" | "secondary" | "danger"
// Result: "btn-primary" | "btn-secondary" | "btn-danger"

Exercise 4: Mapped Types

// Create a mapped type 'Nullable' that:
// - Makes all properties nullable
// - Example: Nullable<{ a: string }> becomes { a: string | null }

Exercise 5: Conditional Types

// Create a conditional type 'Flatten' that:
// - If T is an array, return the element type
// - Otherwise, return T
// Example: Flatten<string[]> => string

Best Practices

  1. Use keyof for type-safe property access - Prevents typos
  2. Use typeof for type extraction - When you have a value, not a type
  3. Template literals for string patterns - Type-safe string manipulation
  4. Mapped types for transformations - Reusable type utilities
  5. Conditional types for logic - Type-level programming

Common Mistakes

  1. Confusing keyof with typeof
// keyof gets keys of a type
type Keys = keyof User; // "name" | "age"

// typeof gets type of a value
type UserType = typeof user; // { name: string; age: number }
  1. Not understanding distribution
// Distributes over union
type T = ToArray<string | number>; // string[] | number[]

// Doesn't distribute
type T = ToArrayNonDist<string | number>; // (string | number)[]
  1. Index signature conflicts
// ❌ Error: Numeric index must be compatible with string index
interface Bad {
  [key: string]: string;
  [key: number]: number; // Incompatible
}

Next Steps

Learn about Modules and Namespaces.