| |

TypeScript 29 🔷 keyof, typeof, and Indexed Access Types

Three type operators sit at the heart of TypeScript’s type-level programming: keyof gives you the union of an object’s keys, typeof gives you the type of a value, and indexed access types (T[K]) give you the type of a property. Together they let you derive new types from existing ones — extracting keys, resolving values, and expressing relationships between types without duplicating them. They’re the foundation of mapped types, conditional types, and every utility type in the standard library. Once you understand them, a whole layer of TypeScript opens up.

Key point: keyof T produces a union of T‘s property names. typeof x produces the type of the value x. T[K] produces the type of the property K in T. They combine: typeof user gives the shape, keyof typeof user gives the keys, and (typeof user)[keyof typeof user] gives the value union. Every complex type-level operation starts with these three.


keyof — the union of keys

keyof T produces a union of all of T‘s property names.

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

type UserKeys = keyof User;
// 'id' | 'name' | 'email'

keyof User is a union of the three string literals. It’s a type — you can use it anywhere a type goes.

Using keyof in a type:

type UserKey = keyof User;

const key1: UserKey = 'id';       // ✅
const key2: UserKey = 'name';     // ✅
const key3: UserKey = 'missing';  // ❌

The union restricts what values are allowed.

Using keyof in a function:

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

const user: User = { id: 1, name: 'Alice', email: 'alice@example.com' };
get(user, 'name');    // string
get(user, 'id');      // number
get(user, 'missing'); // ❌

K extends keyof T requires key to be an actual key of obj. The return type T[K] is the type of that property.

keyof on an interface with an index signature:

interface Dict {
  [key: string]: number;
}

type Keys = keyof Dict;
// string | number

An index signature adds string (or number) to the key union. That’s why Dict‘s keys are string | number — JavaScript objects can be indexed by both.

keyof on an array:

type ArrayKeys = keyof string[];
// number | 'length' | 'push' | 'pop' | 'map' | ...

An array’s keys include number (for indices) and all the method names.

keyof on a class:

class User {
  id = 0;
  name = '';
  greet(): string { return `Hi, ${this.name}`; }
}

type UserKeys = keyof User;
// 'id' | 'name' | 'greet'

Class instances have both properties and methods. keyof includes both.

keyof on a type with no members:

type Empty = keyof {};
// never

An empty object has no keys, so the union is never.

Why keyof matters: It exposes the keys of a type as a union, which you can constrain type parameters against. Any function that takes a “property name” should use K extends keyof T. It’s the standard way to express “this parameter must be a key of that object.”

Why keyof is idiomatic: It ties one type to another. A function that “gets a property by name” must know that name is a key of the object. keyof provides the union; the constraint enforces membership. The compiler checks every call — typos fail, wrong types fail, and the return type is precise.


typeof — the type of a value

typeof x produces the type of the value x — at the type level.

const user = {
  id: 1,
  name: 'Alice',
  email: 'alice@example.com'
};

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

typeof user extracts the type from the value user. You don’t have to write the shape twice — you derive it from the value.

typeof on primitives:

const name = 'Alice';
type Name = typeof name;      // 'Alice' (const infers literal)

let count = 42;
type Count = typeof count;    // number

typeof reflects the inferred type of the variable. const gives the literal; let gives the widened type.

typeof on functions:

function greet(name: string): string {
  return `Hello, ${name}`;
}

type Greet = typeof greet;
// (name: string) => string

typeof greet is the function’s type — parameters and return type.

typeof on classes:

class User {
  id = 0;
  name = '';
}

type UserConstructor = typeof User;
// { new (): User; prototype: User }

type UserInstance = InstanceType<typeof User>;
// User

typeof User is the constructor type. InstanceType<typeof User> extracts the instance type.

typeof on modules and imports:

import * as fs from 'fs';

type FsType = typeof fs;
// The full type of the fs module

You can capture the type of an entire module.

typeof in a type context:

type User = typeof user;

// Combine with other operators
type UserKeys = keyof typeof user;
// 'id' | 'name' | 'email'

The typeof operator works in type positions — after keyof, inside generics, anywhere a type appears.

What typeof cannot do: It can’t produce a type from a value defined later in the file (hoisting is type-only, not value-order). It also can’t be used on expressions that aren’t simple identifiers or property accesses in some contexts.

Why typeof matters: It lets you derive types from values instead of declaring them twice. Define a constant object, and its type comes from typeof. Define a config, and its keys come from keyof typeof config. This is how you keep a single source of truth — the value — and let the type follow.


Indexed access types — T[K]

An indexed access type extracts the type of a property.

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

type UserId = User['id'];         // number
type UserName = User['name'];     // string

User['id'] is the type of the id property. It’s like accessing the property at runtime, but at the type level.

Multiple keys:

type UserContact = User['name' | 'email'];
// string | string → string

If the keys share a type, the union collapses. If they differ, the result is a union:

type UserValue = User['id' | 'name'];
// number | string

Combining with keyof:

type UserValue = User[keyof User];
// number | string

User[keyof User] gives the union of every property’s type. It’s a common pattern.

Indexed access with a variable key:

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

T[K] is the type of the property named by K. The constraint ensures K is a valid key.

Indexed access on arrays:

type StringArray = string[];
type Element = StringArray[number];
// string

type Tuple = [string, number];
type First = Tuple[0];    // string
type Second = Tuple[1];   // number
type All = Tuple[number]; // string | number

For arrays, [number] gives the element type. For tuples, [0] and [1] give specific positions, [number] gives the union of all elements.

Indexed access on objects with index signatures:

interface Dict {
  [key: string]: number;
}

type Value = Dict[string];    // number

The index signature’s value type is accessible via [string].

Chained indexed access:

interface Company {
  ceo: { name: string; age: number };
}

type CeoName = Company['ceo']['name'];
// string

You can chain — Company['ceo'] is an object, and ['name'] on it gives the name’s type.

Indexed access as a constraint:

function set<T, K extends keyof T>(
  obj: T,
  key: K,
  value: T[K]
): void {
  obj[key] = value;
}

value: T[K] means the value must match the property’s type. Setting user.name requires a string; setting user.id requires a number. The compiler catches mismatches.

Why indexed access matters: It’s how you express “the type of this property.” Without it, you’d have to redeclare the type. With it, you derive it. The combination with keyofT[keyof T] — gives the union of all property types, which is used in mapped types, conditional types, and utilities like Partial<T>.

Why indexed access is powerful: It reads types the way code reads values. user.name gets the value; User['name'] gets the type. That symmetry is what makes TypeScript’s type-level programming feel like writing normal code — you’re accessing properties of types the same way you access properties of objects.


The three together

The operators combine naturally.

keyof typeof — keys of a value’s type:

const config = {
  apiUrl: 'https://api.example.com',
  timeout: 5000,
  retries: 3
};

type ConfigKey = keyof typeof config;
// 'apiUrl' | 'timeout' | 'retries'

function getConfig(key: ConfigKey) {
  return config[key];
}

getConfig('apiUrl');     // ✅
getConfig('missing');    // ❌

typeof config gives the shape; keyof gives the keys. Now you can iterate or pass valid keys.

typeof x[keyof typeof x] — value union:

type ConfigValue = typeof config[keyof typeof config];
// string | number

Every value in the config is either a string or a number. This pattern — typeof X[keyof typeof X] — is the standard way to extract the value union from a constant object.

From a tuple:

const ROLES = ['admin', 'user', 'guest'] as const;

type Role = typeof ROLES[number];
// 'admin' | 'user' | 'guest'

typeof ROLES is the tuple type; [number] extracts the element union.

From a config object:

const STATUS = {
  Idle: 'idle',
  Loading: 'loading',
  Ready: 'ready'
} as const;

type StatusKey = keyof typeof STATUS;
// 'Idle' | 'Loading' | 'Ready'

type StatusValue = typeof STATUS[keyof typeof STATUS];
// 'idle' | 'loading' | 'ready'

This is the source-of-truth pattern: one runtime object, derived types for keys and values. Add a key, and both unions update.

From an enum-like object:

const HttpMethods = {
  GET: 'GET',
  POST: 'POST',
  PUT: 'PUT',
  DELETE: 'DELETE'
} as const;

type HttpMethod = typeof HttpMethods[keyof typeof HttpMethods];
// 'GET' | 'POST' | 'PUT' | 'DELETE'

Replaces an enum with a plain object and derived types — no runtime enum code, full type safety.

Why these combinations are everywhere: They let you derive types from values. Define a constant, and its keys and values become types. Add a key, and every union grows. This is the single-source-of-truth pattern applied to types — one place to change, and the type system follows.

Why typeof X[keyof typeof X] is idiomatic: It’s the standard way to extract “all possible values” from a constant object. You’ll see it in every codebase that uses as const objects for enums, config, or routes. It’s concise and precise — one expression that says “any value from this object.”


Practical patterns

The operators power a lot of real TypeScript.

Type-safe property getter:

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

const user = { id: 1, name: 'Alice' };
get(user, 'name');  // string
get(user, 'id');    // number

Type-safe property setter:

function set<T, K extends keyof T>(obj: T, key: K, value: T[K]): void {
  obj[key] = value;
}

set(user, 'name', 'Bob');  // ✅
set(user, 'name', 42);     // ❌ string expected

Pluck multiple properties:

function pluck<T, K extends keyof T>(items: T[], key: K): T[K][] {
  return items.map(item => item[key]);
}

Pick properties:

function pick<T, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> {
  return keys.reduce((acc, k) => ({ ...acc, [k]: obj[k] }), {} as Pick<T, K>);
}

Iterate over keys:

function keys<T extends object>(obj: T): (keyof T)[] {
  return Object.keys(obj) as (keyof T)[];
}

Group by a key:

function groupBy<T, K extends keyof T>(items: T[], key: K): Map<T[K], T[]> {
  const map = new Map<T[K], T[]>();
  for (const item of items) {
    const k = item[key];
    const list = map.get(k) ?? [];
    list.push(k === undefined ? item : item);
    map.set(k, list);
  }
  return map;
}

Sort by a key:

function sortBy<T, K extends keyof T>(items: T[], key: K): T[] {
  return [...items].sort((a, b) => {
    const av = a[key];
    const bv = b[key];
    return av < bv ? -1 : av > bv ? 1 : 0;
  });
}

Each pattern uses keyof to constrain and T[K] to type the result.

Why these patterns are worth knowing: They cover most of what you’ll do with objects — read, write, iterate, sort, group, pick. Each is generic and type-safe. Once you know them, adapting to specific needs is quick, and the compiler catches mistakes.


A full example

A type-safe form handler using all three operators.

// ============================================
// FORM SHAPE
// ============================================

interface FormData {
  name: string;
  email: string;
  age: number;
  newsletter: boolean;
}

// ============================================
// TYPES DERIVED FROM THE SHAPE
// ============================================

type FormKey = keyof FormData;
// 'name' | 'email' | 'age' | 'newsletter'

type FormValue = FormData[FormKey];
// string | number | boolean

type FormErrors = Partial<Record<FormKey, string>>;
// { name?: string; email?: string; age?: string; newsletter?: string }

type FieldType<K extends FormKey> = FormData[K];

// ============================================
// FORM CLASS
// ============================================

class Form<K extends FormKey = FormKey> {
  private values: Partial<FormData> = {};
  private errors: FormErrors = {};

  constructor(private fields: K[]) {}

  set<K2 extends K>(field: K2, value: FormData[K2]): void {
    this.values[field] = value;
    delete this.errors[field];
  }

  get<K2 extends K>(field: K2): FormData[K2] | undefined {
    return this.values[field] as FormData[K2] | undefined;
  }

  getValues(): Partial<FormData> {
    return { ...this.values };
  }

  getErrors(): FormErrors {
    return { ...this.errors };
  }

  setError<K2 extends K>(field: K2, message: string): void {
    this.errors[field] = message;
  }

  isValid(): boolean {
    return Object.keys(this.errors).length === 0;
  }

  // Iterate the fields with proper typing
  forEach(callback: <K2 extends K>(field: K2, value: FormData[K2] | undefined) => void): void {
    for (const field of this.fields) {
      callback(field, this.values[field] as FormData[typeof field] | undefined);
    }
  }
}

// ============================================
// USAGE
// ============================================

const form = new Form(['name', 'email', 'age', 'newsletter']);

form.set('name', 'Alice');         // ✅
form.set('age', 30);               // ✅
form.set('newsletter', true);      // ✅
form.set('name', 42);              // ❌ string expected
form.set('missing', 'x');          // ❌ not a field

form.setError('email', 'Required');  // ✅
form.setError('name', 'Too short');  // ✅

console.log(form.getValues());
console.log(form.getErrors());
console.log('Valid:', form.isValid());

// Typed getter
const name = form.get('name');
// string | undefined

const age = form.get('age');
// number | undefined

form.forEach((field, value) => {
  console.log(`${field}: ${value}`);
});

What this shows:

  • FormKey — the union of form field names
  • FormValue — the union of all value types
  • FormErrors — a mapped type keyed by field names
  • FieldType<K> — the type of a specific field
  • set and get — constrained by keyof FormData and typed by FormData[K]

Every access is type-checked. Setting a string to age fails; reading name gives string | undefined.

Why this shape: It’s a realistic form handler where every operation preserves type information. form.set('age', 30) is checked; form.get('name') returns the right type. The compiler enforces the relationship between field names and value types.


Complete Example Session

# ============================================
# PART 1: KEYOF
# ============================================

cat > keyof.ts << 'EOF'
interface User {
  id: number;
  name: string;
  email: string;
}

type Keys = keyof User;
// 'id' | 'name' | 'email'

const k1: Keys = 'id';      // ✅
const k2: Keys = 'name';    // ✅
// const k3: Keys = 'x';    // ❌

console.log(k1, k2);
EOF

npx tsc --noEmit keyof.ts
# (no errors)

# ============================================
# PART 2: TYPEOF
# ============================================

cat > typeof.ts << 'EOF'
const user = {
  id: 1,
  name: 'Alice',
  email: 'alice@example.com'
};

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

const u: User = { id: 2, name: 'Bob', email: 'bob@example.com' };
console.log(u);
EOF

npx tsc --noEmit typeof.ts
# (no errors)

# ============================================
# PART 3: INDEXED ACCESS
# ============================================

cat > indexed.ts << 'EOF'
interface User {
  id: number;
  name: string;
  email: string;
}

type UserId = User['id'];        // number
type UserName = User['name'];    // string
type UserValue = User[keyof User];  // number | string

const id: UserId = 1;
const n: UserName = 'Alice';
const v: UserValue = 42;

console.log(id, n, v);
EOF

npx tsc --noEmit indexed.ts
# (no errors)

# ============================================
# PART 4: ARRAY AND TUPLE INDEXED ACCESS
# ============================================

cat > array.ts << 'EOF'
type Strings = string[];
type Element = Strings[number];   // string

type Tuple = [string, number, boolean];
type First = Tuple[0];            // string
type Second = Tuple[1];           // number
type All = Tuple[number];         // string | number | boolean

const e: Element = 'x';
const f: First = 'y';
const a: All = true;

console.log(e, f, a);
EOF

npx tsc --noEmit array.ts
# (no errors)

# ============================================
# PART 5: COMBINED PATTERNS
# ============================================

cat > combined.ts << 'EOF'
const config = {
  apiUrl: 'https://api.example.com',
  timeout: 5000,
  retries: 3
} as const;

type ConfigKey = keyof typeof config;
// 'apiUrl' | 'timeout' | 'retries'

type ConfigValue = typeof config[keyof typeof config];
// 'https://api.example.com' | 5000 | 3

function getConfig(key: ConfigKey): ConfigValue {
  return config[key];
}

console.log(getConfig('apiUrl'));
console.log(getConfig('timeout'));

// Add a const array
const ROLES = ['admin', 'user', 'guest'] as const;
type Role = typeof ROLES[number];
// 'admin' | 'user' | 'guest'

function setRole(r: Role): void { console.log(r); }
setRole('admin');
// setRole('owner');  // ❌
EOF

npx tsc --noEmit combined.ts
# (no errors)

# ============================================
# PART 6: GENERIC GET/SET
# ============================================

cat > getset.ts << 'EOF'
function get<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

function set<T, K extends keyof T>(obj: T, key: K, value: T[K]): void {
  obj[key] = value;
}

const user = { id: 1, name: 'Alice', active: true };

const n = get(user, 'name');   // string
const i = get(user, 'id');     // number

set(user, 'name', 'Bob');
set(user, 'active', false);
// set(user, 'name', 42);   // ❌

console.log(n, i, user);
EOF

npx tsc --noEmit getset.ts
# (no errors)

# ============================================
# PART 7: COMPILE AND RUN
# ============================================

npx tsc keyof.ts typeof.ts indexed.ts array.ts combined.ts getset.ts
node keyof.js
# [ id name ]

node typeof.js
# [ { id: 2, name: 'Bob', email: 'bob@example.com' } ]

node indexed.js
# [ 1 Alice 42 ]

node array.js
# [ x y true ]

node combined.js
# [ https://api.example.com ]
# [ 5000 ]
# [ admin ]

node getset.js
# [ Alice 1 { id: 1, name: 'Bob', active: false } ]

Quick Reference

The Three Operators

OperatorMeaningExample
keyof TUnion of T’s keyskeyof User'id' | 'name'
typeof xType of value xtypeof user → shape
T[K]Type of property KUser['id']number

keyof Results

Typekeyof
InterfaceUnion of property names
ClassUnion of properties + methods
Arraynumber | 'length' | 'push' | ...
Object with index sigstring | number
Empty objectnever
Union of typesUnion of keys (common ones)

typeof Results

Valuetypeof
const x = 55
let x = 5number
const obj = { a: 1 }{ a: number }
const arr = [1, 2]number[]
function f(): void {}() => void
class C {}constructor type
as const objectreadonly literal shape

Indexed Access Results

Type[K]
User['id']number
User[keyof User]number | string
string[] [number]string
[string, number][0]string
[string, number][number]string | number
Dict[string]the value type

Combined Patterns

PatternResult
keyof typeof xKeys of x‘s type
typeof x[keyof typeof x]Union of all values
typeof arr[number]Element union from const array
T[keyof T]Value union of T

Common Use Cases

Use casePattern
Type-safe getter<T, K extends keyof T>(o: T, k: K): T[K]
Type-safe setter<T, K extends keyof T>(o: T, k: K, v: T[K])
Pluck<T, K extends keyof T>(xs: T[], k: K): T[K][]
Keys unionkeyof typeof config
Values uniontypeof config[keyof typeof config]
Enum from arraytypeof ARR[number]

Const Object Pattern

const STATUS = {
  Idle: 'idle',
  Ready: 'ready'
} as const;

type Key = keyof typeof STATUS;
// 'Idle' | 'Ready'

type Value = typeof STATUS[keyof typeof STATUS];
// 'idle' | 'ready'

Array/Tuple Access

TypeAccessResult
string[][number]string
readonly string[][number]string
[A, B][0]A
[A, B][1]B
[A, B][number]A | B

Errors and Fixes

ErrorCauseFix
Property 'x' does not existNot a keyUse keyof T
Cannot use typeof on ...Not a valuetypeof needs a value
Type 'K' cannot be used to indexNot a key of TConstrain to keyof T
Object is possibly undefinedOptional propertyCheck or use !

Utility Types Built on These

TypeImplementation
Pick<T, K>{ [P in K]: T[P] }
Omit<T, K>Pick<T, Exclude<keyof T, K>>
Partial<T>{ [P in keyof T]?: T[P] }
Required<T>{ [P in keyof T]-?: T[P] }
Record<K, V>{ [P in K]: V }
Readonly<T>{ readonly [P in keyof T]: T[P] }

Best Practices

Do This:

// Constrain type parameters to keys
function get<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}                                                          // ✅

// Use typeof to derive types from values
const user = { id: 1, name: 'Alice' };
type User = typeof user;                                   // ✅

// Use keyof typeof for keys of a value
const config = { apiUrl: '', timeout: 5000 };
type ConfigKey = keyof typeof config;                      // ✅

// Extract value unions from const objects
const ROLES = ['admin', 'user'] as const;
type Role = typeof ROLES[number];                          // ✅

// Type the setter's value by the property
function set<T, K extends keyof T>(o: T, k: K, v: T[K]): void { } // ✅

// Use indexed access for property types
type UserId = User['id'];                                  // ✅

// Chain indexed access
type City = Company['ceo']['address']['city'];             // ✅

// Use `as const` with typeof for literal types
const STATUS = { Idle: 'idle' } as const;
type Status = typeof STATUS[keyof typeof STATUS];          // ✅

Don’t Do This:

// Don't use string instead of keyof
function get<T>(obj: T, key: string) {
  return obj[key];  // ❌ T has no index signature         // ❌
}

// Don't duplicate types that typeof can derive
interface User { id: number; name: string; }
const u: User = { id: 1, name: 'Alice' };
// Instead of writing the interface, use typeof:           // ✅
const u2 = { id: 1, name: 'Alice' };
type User2 = typeof u2;

// Don't forget as const for literal unions
const ROLES = ['admin', 'user'];  // string[]               // ⚠️
type Role = typeof ROLES[number];  // string                 // ⚠️

// Don't use T[K] without constraining K
function get<T, K>(obj: T, key: K): T[K] {
  return obj[key];  // ❌ K not a key of T                   // ❌
}

// Don't assume typeof works on expressions
type X = typeof (1 + 2);  // ⚠️  works but unusual           // ⚠️

// Don't use indexed access for methods
type Bad = User['greet'];  // ⚠️  may be a function type      // ⚠️

// Don't skip the constraint in a generic setter
function set<T, K extends keyof T>(o: T, k: K, v: any): void { }  // ⚠️

// Don't use keyof on primitives
type Bad = keyof number;  // ❌ 'toString' | 'valueOf' | ...  // ⚠️

Common Pitfalls

PitfallProblemSolution
keyof on anyReturns string | number | symbolType properly
Missing as constWidened to stringAdd as const
Unconstrained KCan’t indexK extends keyof T
T[K] on non-keyCompile errorConstrain first
typeof on typeValue onlyUse T for types
Chained accessNeed all keysMake sure each link exists
Optional propertiesT[K] includes undefinedUse -? modifier or handle
Index signature wideningKeys become stringExplicit type

Real-World Examples

1. keyof on interface

interface User { id: number; name: string; }
type Keys = keyof User;  // 'id' | 'name'

2. typeof on const

const user = { id: 1, name: 'Alice' };
type User = typeof user;

3. Indexed access

type UserId = User['id'];  // number

4. Combined — keys of a value

const config = { api: '', timeout: 5000 };
type Key = keyof typeof config;

5. Combined — values of a value

type Value = typeof config[keyof typeof config];

6. Const array element union

const COLORS = ['red', 'green', 'blue'] as const;
type Color = typeof COLORS[number];

7. Const object value union

const STATUS = { Idle: 'idle', Ready: 'ready' } as const;
type Status = typeof STATUS[keyof typeof STATUS];

8. Type-safe getter

function get<T, K extends keyof T>(o: T, k: K): T[K] {
  return o[k];
}

9. Type-safe setter

function set<T, K extends keyof T>(o: T, k: K, v: T[K]): void {
  o[k] = v;
}

10. Pluck

function pluck<T, K extends keyof T>(xs: T[], k: K): T[K][] {
  return xs.map(x => x[k]);
}

11. Pick

function pick<T, K extends keyof T>(o: T, ks: K[]): Pick<T, K> {
  return ks.reduce((a, k) => ({ ...a, [k]: o[k] }), {} as Pick<T, K>);
}

12. Array element type

type Element = string[][number];  // string

13. Tuple element

type Pair = [string, number];
type First = Pair[0];   // string
type Second = Pair[1];  // number

14. Tuple union

type Both = Pair[number];  // string | number

15. Chained access

interface Company { ceo: { name: string } }
type CeoName = Company['ceo']['name'];  // string

16. Function type from value

function greet(name: string): string { return name; }
type Greet = typeof greet;  // (name: string) => string

17. Class constructor type

class User { }
type Ctor = typeof User;  // constructor type

18. Instance type

class User { name = ''; }
type Instance = InstanceType<typeof User>;  // User

19. Iterating keys

function keys<T extends object>(o: T): (keyof T)[] {
  return Object.keys(o) as (keyof T)[];
}

20. Generic form field

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

Visual: The Three Operators

┌──────────────────────────────────────────────┐
│  keyof                                       │
│                                              │
│  keyof User                                  │
│      │                                       │
│      ▼                                       │
│  'id' | 'name' | 'email'                     │
│                                              │
│  → union of keys                             │
│                                              │
└──────────────────────────────────────────────┘

┌──────────────────────────────────────────────┐
│  typeof                                      │
│                                              │
│  const user = { id: 1, name: 'Alice' }       │
│      │                                       │
│      ▼                                       │
│  typeof user                                 │
│      │                                       │
│      ▼                                       │
│  { id: number; name: string }                │
│                                              │
│  → type of value                             │
│                                              │
└──────────────────────────────────────────────┘

┌──────────────────────────────────────────────┐
│  Indexed access T[K]                         │
│                                              │
│  User['id']                                  │
│      │                                       │
│      ▼                                       │
│  number                                      │
│                                              │
│  → type of property                          │
│                                              │
└──────────────────────────────────────────────┘

Visual: Combined Usage

┌──────────────────────────────────────────────┐
│  const config = {                            │
│    apiUrl: 'https://x',                      │
│    timeout: 5000                             │
│  } as const;                                 │
│                                              │
└──────────────────────────────────────────────┘
       │                              │
       │  keyof typeof                │  typeof X[keyof typeof X]
       ▼                              ▼
┌──────────────────────┐   ┌──────────────────────┐
│  'apiUrl' | 'timeout'│   │  'https://x' | 5000  │
│                      │   │                      │
│  keys of config      │   │  values of config    │
└──────────────────────┘   └──────────────────────┘

Visual: Type-Safe Getter

┌──────────────────────────────────────────────┐
│  function get<T, K extends keyof T>(         │
│    obj: T,                                   │
│    key: K                                    │
│  ): T[K] {                                   │
│    return obj[key];                          │
│  }                                           │
│                                              │
└──────────────────────────────────────────────┘
                  │
                  │  called with
                  ▼
┌──────────────────────────────────────────────┐
│  get(user, 'name')                           │
│       │                                      │
│       ▼                                      │
│  K = 'name'                                  │
│  T[K] = string                               │
│                                              │
│  get(user, 'id')                             │
│  K = 'id'                                    │
│  T[K] = number                               │
│                                              │
│  get(user, 'x')                              │
│  ❌ not keyof User                           │
│                                              │
└──────────────────────────────────────────────┘

Visual: typeof on Values

┌──────────────────────────────────────────────┐
│  Value → Type                                │
│                                              │
│  const x = 5                                 │
│  type X = typeof x   → 5                     │
│                                              │
│  let y = 5                                   │
│  type Y = typeof y   → number                │
│                                              │
│  const obj = { a: 1 }                        │
│  type O = typeof obj → { a: number }         │
│                                              │
│  const arr = [1, 2] as const                 │
│  type A = typeof arr → readonly [1, 2]       │
│                                              │
└──────────────────────────────────────────────┘

Visual: Array and Tuple Access

┌──────────────────────────────────────────────┐
│  type Element = string[][number]             │
│  // string                                   │
│                                              │
└──────────────────────────────────────────────┘

┌──────────────────────────────────────────────┐
│  type Tuple = [string, number]               │
│                                              │
│  Tuple[0]      → string                      │
│  Tuple[1]      → number                      │
│  Tuple[number] → string | number             │
│                                              │
└──────────────────────────────────────────────┘

┌──────────────────────────────────────────────┐
│  const ROLES = ['admin', 'user'] as const;   │
│                                              │
│  typeof ROLES              → readonly [...]  │
│  typeof ROLES[number]      → 'admin' | 'user'│
│  typeof ROLES[0]           → 'admin'         │
│                                              │
└──────────────────────────────────────────────┘

Visual: Source of Truth Pattern

┌──────────────────────────────────────────────┐
│  Runtime value (source of truth)             │
│                                              │
│  const STATUS = {                            │
│    Idle: 'idle',                             │
│    Ready: 'ready'                            │
│  } as const;                                 │
│                                              │
└──────────────────────────────────────────────┘
                  │
                  │  derive types
                  ▼
┌──────────────────────────────────────────────┐
│  Types                                       │
│                                              │
│  type Key = keyof typeof STATUS              │
│  // 'Idle' | 'Ready'                         │
│                                              │
│  type Value = typeof STATUS[keyof typeof STATUS]│
│  // 'idle' | 'ready'                         │
│                                              │
│  Add a key to STATUS → types update          │
│                                              │
└──────────────────────────────────────────────┘

Visual: Utility Types Are Built on These

┌──────────────────────────────────────────────┐
│  Pick<T, K>                                  │
│  = { [P in K]: T[P] }                        │
│         ↑        ↑                           │
│         keyof    indexed access              │
│                                              │
│  Partial<T>                                  │
│  = { [P in keyof T]?: T[P] }                 │
│             ↑          ↑                     │
│             keyof      indexed access        │
│                                              │
│  Omit<T, K>                                  │
│  = Pick<T, Exclude<keyof T, K>>              │
│                    ↑                         │
│                    keyof                     │
│                                              │
└──────────────────────────────────────────────┘

Visual: Chained Access

┌──────────────────────────────────────────────┐
│  interface Company {                         │
│    ceo: {                                    │
│      name: string;                           │
│      address: { city: string };              │
│    };                                        │
│  }                                           │
│                                              │
│  Company['ceo']              → { name, address }│
│  Company['ceo']['address']   → { city }      │
│  Company['ceo']['address']['city']  → string │
│                                              │
└──────────────────────────────────────────────┘

Visual: Decision Flow

┌──────────────────────────────────────────────┐
│  Need the keys of a type?                    │
│       │                                      │
│       └── Yes ──► keyof T                    │
│                                              │
└──────────────────────────────────────────────┘

┌──────────────────────────────────────────────┐
│  Have a value but need its type?             │
│       │                                      │
│       └── Yes ──► typeof value               │
│                                              │
└──────────────────────────────────────────────┘

┌──────────────────────────────────────────────┐
│  Need a property's type?                     │
│       │                                      │
│       └── Yes ──► T[K]                       │
│                                              │
└──────────────────────────────────────────────┘

┌──────────────────────────────────────────────┐
│  Need the keys of a value's type?            │
│       │                                      │
│       └── Yes ──► keyof typeof value         │
│                                              │
└──────────────────────────────────────────────┘

┌──────────────────────────────────────────────┐
│  Need the values' union from a const object? │
│       │                                      │
│       └── Yes ──► typeof X[keyof typeof X]   │
│                                              │
└──────────────────────────────────────────────┘

Summary

OperatorMeaningExample
keyof TUnion of keyskeyof User'id' | 'name'
typeof xType of valuetypeof user → shape
T[K]Property typeUser['id']number
keyof typeof xKeys of value’s type'apiUrl' | 'timeout'
typeof x[keyof typeof x]Value union'a' | 5
typeof arr[number]Element union'admin' | 'user'

Key takeaways:

  • keyof T gives the union of T‘s property names
  • typeof x gives the type of a value — a bridge from values to types
  • T[K] gives the type of property K in T
  • keyof + typeofkeyof typeof x gives the keys of a value’s type
  • typeof X[keyof typeof X] gives the union of all values in a const object
  • typeof arr[number] gives the element union of a as const array
  • Constrain type parameters with K extends keyof T for type-safe property access
  • T[K] makes setters type-safe — set(obj, 'name', 42) fails
  • Arrays and tuples support indexed access — T[number], T[0]
  • Utility types like Pick, Omit, Partial, Record are built on these operators
  • as const preserves literal types — needed for value unions
  • Source-of-truth pattern — one runtime object, derived types for keys and values

Remember: keyof, typeof, and indexed access are the three primitives of TypeScript’s type-level programming. keyof exposes keys, typeof bridges values to types, and T[K] reads property types. Combine them — keyof typeof config, typeof STATUS[keyof typeof STATUS] — and you can derive any type from a value, keep a single source of truth, and write functions that are precise about what they accept. Every utility type and every mapped type is built on these three. Learning them opens the door to the rest of TypeScript’s type system.


Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!