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 | numberThe 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 userWithout 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 statusThis 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 eventBest 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 Tproduces a union of all property name types inTtypeof valueproduces the TypeScript type of a runtime valuekeyof typeof objis the standard pattern for deriving key unions from runtime objectsK extends keyof Tconstrains a generic to valid property names, enabling type-safe access without castingtypeofis 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.