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
- Use keyof for type-safe property access - Prevents typos
- Use typeof for type extraction - When you have a value, not a type
- Template literals for string patterns - Type-safe string manipulation
- Mapped types for transformations - Reusable type utilities
- Conditional types for logic - Type-level programming
Common Mistakes
- 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 }
- Not understanding distribution
// Distributes over union
type T = ToArray<string | number>; // string[] | number[]
// Doesn't distribute
type T = ToArrayNonDist<string | number>; // (string | number)[]
- 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.