Generic Constraints & keyof Operator

Restrict generic type parameters using the extends keyword and inspect object keys using the keyof operator.

1. Generic Constraints (`extends`)

Sometimes you want generics to only accept types that have specific properties (like a length property):

interface Lengthwise {
  length: number;
}

// T is constrained to types that have a .length property!
function logLength<T extends Lengthwise>(item: T): number {
  console.log("Length is:", item.length);
  return item.length;
}

logLength("Hello World"); // Works! String has .length
logLength([1, 2, 3, 4]);   // Works! Array has .length
// ❌ Error: Argument of type 'number' is not assignable to 'Lengthwise'
// logLength(123);

2. The `keyof` Operator

The keyof operator takes an object type and produces a union string literal of its key names:

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

// UserKeys is "id" | "name" | "email"
type UserKeys = keyof User;

3. Safe Object Property Getter (`K extends keyof T`)

Combine generics with keyof to create type-safe helper functions that guarantee a key exists on an object:

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

const person = { name: "Alice", age: 30, isDeveloper: true };

const userName = getProperty(person, "name"); // Type is string
const userAge = getProperty(person, "age");   // Type is number

// ❌ Error: Argument of type '"salary"' is not assignable to parameter of type '"name" | "age" | "isDeveloper"'
// getProperty(person, "salary");

Next Up

Master built-in TypeScript utility types: Partial, Required, Readonly, Record, Pick, and Omit.

Next Lesson: Built-in Utility Types →