Skip to content
JL

Home

About

Blog

Contact

Shop

Portfolio

Privacy

TOS

Click to navigate

  1. Home
  2. Joshua R. Lehman's Blog
  3. keyof and typeof Operators in TypeScript

Table of contents

  • Share on X
  • Discuss on X

Related Articles

Mapped Types: Transforming Types in TypeScript
TypeScript
10m
Jul 12, 2026

Mapped Types: Transforming Types in TypeScript

Mapped types are TypeScript's for-loop for types. They iterate over the keys of an existing type and produce a new type with transformed properties — the same way Array.map transforms every element in an array.

#Mapped Types#TypeScript Transformation+5
The infer Keyword in TypeScript
TypeScript
9m
Jul 5, 2026

The infer Keyword in TypeScript

The infer keyword is TypeScript's type-level pattern match. Inside a conditional type, it captures an unknown part of a type so you can use it on the right-hand side — the same way a regex capture group pulls out part of a string.

#infer Keyword#TypeScript Conditional Types+5
Default Generic Parameters in TypeScript
TypeScript
6m
Jun 21, 2026

Default Generic Parameters in TypeScript

Default generic parameters let you make type arguments optional by providing a fallback type when the caller doesn't specify one. They're the reason Result<User> works without writing Result<User, Error> every time — the Error default is already baked in.

#Default Generic Parameters#TypeScript Generics+4
Ask me anything! 💬

© Joshua R. Lehman

Full Stack Developer

Crafted with passion • Built with modern web technologies

2026 • All rights reserved

Contents

  • The keyof Operator
    • keyof on Index Signatures
  • Using keyof with Generics
  • The typeof Operator
    • typeof on Functions
  • Combining keyof and typeof
  • keyof in Mapped Types
  • Real-World Usage
  • Best Practices
  • What Is Next
  • Key Takeaways
TypeScript

keyof and typeof Operators in TypeScript

July 19, 2026•6 min read
Joshua R. Lehman
Joshua R. Lehman
Author
TypeScript keyof and typeof operators for type queries and property access
keyof and typeof Operators in TypeScript

keyof and typeof are TypeScript's type query operators — they let you derive types from what already exists rather than writing them by hand. keyof turns an object type into a union of its property name strings. typeof turns a runtime value into its TypeScript type. Together they're the foundation for type-safe property access patterns and the building blocks for mapped types, conditional types, and indexed access types. In this post, you'll learn how both operators work, where they're most useful, and how to combine them for powerful type-level queries.

Two Levels of typeof

JavaScript already has a runtime typeof operator that returns strings like "string" or "object". TypeScript's typeof operates at the type level — it extracts the TypeScript type of a variable. They look the same in code but operate in completely different contexts.

The keyof Operator

keyof T produces a union type of all the property names of T:

interface User {
  id: number;
  name: string;
  email: string;
  active: boolean;
}
 
type UserKeys = keyof User;
// "id" | "name" | "email" | "active"
 
// Works with type aliases too
type Point = { x: number; y: number };
type PointKeys = keyof Point; // "x" | "y"

For types with only number keys, keyof produces number. For types with only string keys, it produces string. For mixed or specific keys, it produces the exact union:

type NumericObject = { [n: number]: string };
type K1 = keyof NumericObject; // number
 
type StringObject = { [s: string]: boolean };
type K2 = keyof StringObject; // string | number
// (because number keys are coerced to strings in JS, number is included)

keyof on Index Signatures

type Dictionary = { [key: string]: unknown };
type DictKeys = keyof Dictionary; // string | number

The number appears because JavaScript allows numeric property access on any object — obj[0] is equivalent to obj["0"].

Using keyof with Generics

The most common use of keyof is constraining a generic parameter to valid property names:

// Type-safe property getter
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}
 
const user = { id: 1, name: "Alice", active: true };
 
const name = getProperty(user, "name"); // string — TypeScript knows this
const id = getProperty(user, "id"); // number
// getProperty(user, "missing");          // Error: not a key of user

Without K extends keyof T, you'd need a cast inside the function. The constraint means TypeScript can prove the return type is exactly T[K] — no any, no cast needed.

K extends keyof T Is Everywhere

This K extends keyof T pattern appears constantly in utility types, generic functions, and library code. Recognizing it immediately tells you "this is a type-safe property access pattern."

The typeof Operator

In type position (after : or in a type expression), typeof extracts the TypeScript type of a variable or property:

const config = {
  host: "localhost",
  port: 3000,
  debug: false,
};
 
type Config = typeof config;
// { host: string; port: number; debug: boolean }
 
const numbers = [1, 2, 3] as const;
type Numbers = typeof numbers;
// readonly [1, 2, 3]

Without typeof, you'd have to define Config as a separate interface and then declare config with that type — keeping them in sync manually. typeof derives the type from the value, making the value the single source of truth.

typeof on Functions

typeof works on functions too, and is often combined with ReturnType and Parameters:

function createUser(name: string, role: "admin" | "user") {
  return { name, role, createdAt: new Date() };
}
 
type CreateUserFn = typeof createUser;
// (name: string, role: "admin" | "user") => { name: string; role: "admin" | "user"; createdAt: Date }
 
type UserShape = ReturnType<typeof createUser>;
// { name: string; role: "admin" | "user"; createdAt: Date }

Combining keyof and typeof

The combination keyof typeof value is common when you have a runtime object and want its keys as a type:

const statusCodes = {
  ok: 200,
  created: 201,
  badRequest: 400,
  notFound: 404,
  serverError: 500,
} as const;
 
type StatusCode = keyof typeof statusCodes;
// "ok" | "created" | "badRequest" | "notFound" | "serverError"
 
type StatusValue = (typeof statusCodes)[StatusCode];
// 200 | 201 | 400 | 404 | 500
 
function getStatusCode(status: StatusCode): number {
  return statusCodes[status];
}
 
getStatusCode("ok"); // fine
getStatusCode("notFound"); // fine
// getStatusCode("teapot");   // Error: not a valid status

This is far better than using string or number for the parameter type — you get autocomplete, exhaustiveness checking, and typo detection.

Const Objects as Enums

keyof typeof myObject on a const object is a common alternative to TypeScript enums. It produces a union of string literals, works with autocomplete, and avoids enum's runtime overhead. Many teams prefer this pattern.

keyof in Mapped Types

You've already seen keyof in mapped types — it's the iteration source:

// Every mapped type uses keyof under the hood
type Optional<T> = {
  [K in keyof T]?: T[K];
};
 
// You can also map over keyof of a specific type
type UserOptional = {
  [K in keyof User]?: User[K];
};

This is also where keyof combines with as for key filtering and remapping (covered in the mapped types post).

Real-World Usage

Here's a common real-world pattern — a type-safe object diff function:

function diff<T extends object>(original: T, updated: Partial<T>): Partial<T> {
  const changes: Partial<T> = {};
  const keys = Object.keys(updated) as Array<keyof T>;
 
  for (const key of keys) {
    if (original[key] !== updated[key]) {
      changes[key] = updated[key];
    }
  }
 
  return changes;
}
 
const before = { id: 1, name: "Alice", role: "user" };
const after = { name: "Alicia", role: "admin" };
 
const changes = diff(before, after);
// TypeScript knows: { id?: number; name?: string; role?: string }

Another pattern: building a type-safe event emitter where event names come from a config object:

const events = {
  userCreated: null,
  userDeleted: null,
  orderPlaced: null,
} as const;
 
type EventName = keyof typeof events;
// "userCreated" | "userDeleted" | "orderPlaced"
 
function emit(event: EventName, payload: unknown) {
  // ...
}
 
emit("userCreated", { id: 1 }); // fine
// emit("userUpdated", {});      // Error: not a valid event

Best Practices

Prefer keyof typeof over enums for string key sets. It's simpler, doesn't require a separate declaration, and the value and type stay in sync automatically.

Use K extends keyof T to constrain property access functions. This is the idiomatic way to write type-safe accessors — it avoids any and gives TypeScript enough information to infer the return type.

Don't over-use typeof. For types you use in many places, define them explicitly. typeof is most useful for types you'd otherwise have to declare twice — once for the value, once for the type.

Watch out for keyof any. In generic contexts, keyof any resolves to string | number | symbol. If you see it in type error messages, it usually means a generic constraint is missing.

Anti-Pattern

Don't write type Keys = "a" | "b" | "c" and then separately write const obj = { a: 1, b: 2, c: 3 }. If the object is the source of truth, derive the keys with keyof typeof obj. If the keys are the source of truth, use them to type the object: const obj: Record<Keys, number>.

What Is Next

You've seen keyof T produce a union of keys, and T[K] access a property type. The next post goes deeper into indexed access types — how T[K] works, how to access nested types, and how to extract array element types.

Key Takeaways

  • keyof T produces a union of all property name types in T
  • typeof value produces the TypeScript type of a runtime value
  • keyof typeof obj is the standard pattern for deriving key unions from runtime objects
  • K extends keyof T constrains a generic to valid property names, enabling type-safe access without casting
  • typeof is most valuable when the value is the source of truth — it keeps type and value in sync automatically

With keyof and typeof, you can build types that stay synchronized with your runtime data. Rename a property, and TypeScript flags every downstream type that needs updating. Add a key, and the union type grows automatically.