Skip to main content

How to constrain a generic type to the keys of another type?

1) Base pattern: an object's key

javascript
function getProp<T, K extends keyof T>(obj: T, key: K): T[K] { return obj[key]; } const user = { id: 1, name: "Tim" }; getProp(user, "id"); // OK getProp(user, "age"); // Error: "age" ∉ keyof typeof user
  • keyof T is the union of the string/number/symbol keys of type T.

2) Allowing only a subset of keys

Sometimes you need not all of T's keys, but only part of them:

javascript
function getIdOrName< T, K extends keyof T & ("id" | "name") >(obj: T, key: K): T[K] { return obj[key]; }
  • Here K is the intersection of "keys of T" with a specific set "id" | "name".

3) Keys whose values match a type (filtering by value)

javascript
type KeysOfType<T, V> = { [K in keyof T]-?: T[K] extends V ? K : never }[keyof T]; function getNumberField< T, K extends KeysOfType<T, number> >(obj: T, key: K): number { return obj[key] as number; } type U = { a: number; b: string; c: number }; /* K = "a" | "c" */

This kind of technique is often called value-based key filtering.


4) Required/optional/non-writable keys

Sometimes you need to restrict keys by a "qualifier" (optional/readonly):

javascript
type RequiredKeys<T> = { [K in keyof T]-?: {} extends Pick<T, K> ? never : K }[keyof T]; type WritableKeys<T> = { [K in keyof T]-?: { [P in K]: T[P] } extends { -readonly [P in K]: T[P] } ? K : never }[keyof T]; function takeRequired<T, K extends RequiredKeys<T>>(obj: T, key: K) { return obj[key]; }

5) Constraining by an index signature's keys

If a type has a dictionary shape:

javascript
type Dict = Record<string, number>; // index signature function getDict< T extends Record<string, unknown>, K extends keyof T >(obj: T, key: K) { return obj[key]; }

keyof T for an index signature is roughly string | number | symbol (depends on the keys).


6) Keys of arrays and tuples

javascript
function first<T extends readonly unknown[]>(arr: T) { const i: Extract<keyof T, number> = 0; // numeric keys only return arr[i]; // T[number] }

For an array, keyof T also includes methods ("length", "push"). To restrict to indexes, intersect with number.


7) Keys of literal objects: as const

To get literal keys instead of "widened" ones:

javascript
const cfg = { api: "/api", cdn: "/cdn", } as const; type Cfg = typeof cfg; // { readonly api: "/api"; readonly cdn: "/cdn" } type CfgKey = keyof Cfg; // "api" | "cdn" function fromCfg<K extends keyof Cfg>(k: K): Cfg[K] { return cfg[k]; }

8) Keys of an enum / lookup objects

javascript
enum Role { Admin = "admin", User = "user" } const ROLES = { Admin: "admin", User: "user" } as const; type RoleKey = keyof typeof ROLES; // "Admin" | "User" type RoleVal = typeof ROLES[RoleKey]; // "admin" | "user" function allow<K extends keyof typeof ROLES>(k: K) { /* ... */ }

9) Utilities for building an API: Pick/Record/Omit with constrained keys

javascript
function pick<T, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> { const out = {} as Pick<T, K>; keys.forEach(k => (out[k] = obj[k])); return out; } function mapValues<T, K extends keyof T>( obj: T, keys: K[], fn: (v: T[K], k: K) => T[K] ): Pick<T, K> { const res = {} as Pick<T, K>; for (const k of keys) res[k] = fn(obj[k], k); return res; }

10) Common pitfalls and tips

  • keyof never -> never: if T can be never, K is already uninferable; add a guard.

  • For arrays, don't forget to filter keys down to number, otherwise the string methods show up too.

  • If the keys come from a value (an array of strings), prefer as const:

    javascript
    const allowed = ["id", "name"] as const; type Allowed = typeof allowed[number]; // "id" | "name" function f<T, K extends keyof T & Allowed>(obj: T, key: K) {}

Summary

  • Base: K extends keyof T, the keys of T.
  • Subset: K extends keyof T & ("a" | "b").
  • By value type: KeysOfType<T, V>.
  • For arrays: Extract<keyof T, number>.
  • For literals/enums: keyof typeof X (+ as const).

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.