TypeScript typeof and keyof Operators in Practice
Master the typeof and keyof type operators to derive types from values and extract object keys for type-safe lookups and mappings.
What you'll learn
- ✓How typeof extracts a type from a runtime value
- ✓How keyof produces a union of an object type's keys
- ✓Combining typeof and keyof for derived types
- ✓Practical patterns for config objects, lookup tables, and event maps
Prerequisites
None — this post is self-contained.
TypeScript includes two type-level operators that derive new types from existing code. The typeof operator extracts a type from a runtime value, letting you avoid writing a type that duplicates a value you already have. The keyof operator produces a union of an object type’s property names. Together, they form the foundation for many advanced TypeScript patterns.
typeof in Type Position
JavaScript has a typeof operator that returns a string like "string" or "number" at runtime. TypeScript adds a second typeof that works at the type level. When typeof appears where a type is expected, it extracts the TypeScript type of a value.
const config = {
apiUrl: 'https://api.example.com',
timeout: 5000,
retries: 3,
debug: false,
};
type Config = typeof config;
// {
// apiUrl: string;
// timeout: number;
// retries: number;
// debug: boolean;
// }
Instead of writing a Config interface by hand and keeping it in sync with the value, typeof derives the type directly. If you add a property to config, the type updates automatically.
typeof With Functions
typeof is especially useful for extracting function types:
function createUser(name: string, age: number) {
return { id: crypto.randomUUID(), name, age, createdAt: new Date() };
}
type CreateUserFn = typeof createUser;
// (name: string, age: number) => { id: string; name: string; age: number; createdAt: Date }
type User = ReturnType<typeof createUser>;
// { id: string; name: string; age: number; createdAt: Date }
The combination of ReturnType and typeof extracts the return type of a function without needing a separate interface. This is invaluable when working with factory functions or API handlers where the return shape is defined by implementation.
typeof With const Assertions
Pairing typeof with as const preserves literal types:
const ROLES = ['admin', 'editor', 'viewer'] as const;
type Role = (typeof ROLES)[number];
// 'admin' | 'editor' | 'viewer'
Without as const, typeof ROLES would be string[], and indexing it would give string. The const assertion preserves the literal values, and [number] indexes into the tuple to produce the union.
const STATUS_CODES = {
OK: 200,
NOT_FOUND: 404,
SERVER_ERROR: 500,
} as const;
type StatusCode = (typeof STATUS_CODES)[keyof typeof STATUS_CODES];
// 200 | 404 | 500
keyof Basics
The keyof operator takes an object type and produces a union of its keys:
interface User {
id: string;
name: string;
email: string;
age: number;
}
type UserKey = keyof User;
// 'id' | 'name' | 'email' | 'age'
This union type restricts values to only valid property names, enabling type-safe property access.
Type-Safe Property Access
The most common use of keyof is constraining a function parameter to valid property names:
function getProperty<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',
age: 30,
};
const name = getProperty(user, 'name'); // string
const age = getProperty(user, 'age'); // number
const bad = getProperty(user, 'address'); // Error: 'address' not in keyof User
The return type T[K] is an indexed access type that resolves to the specific property type. Passing 'name' returns string, passing 'age' returns number.
Combining typeof and keyof
The real power emerges when combining both operators. Given a value, extract its keys as a type:
const eventHandlers = {
click: (x: number, y: number) => { /* ... */ },
keydown: (key: string) => { /* ... */ },
scroll: (offset: number) => { /* ... */ },
};
type EventName = keyof typeof eventHandlers;
// 'click' | 'keydown' | 'scroll'
function emit<K extends keyof typeof eventHandlers>(
event: K,
...args: Parameters<(typeof eventHandlers)[K]>
): void {
eventHandlers[event](...args);
}
emit('click', 100, 200); // valid
emit('keydown', 'Enter'); // valid
emit('scroll', 50); // valid
emit('click', 'wrong'); // Error: string not assignable to number
emit('hover', 0, 0); // Error: 'hover' not in EventName
Each event name is constrained to existing keys, and the arguments are type-checked against the corresponding handler’s parameter types.
Building Lookup Tables
A pattern for configuration-driven code uses keyof to ensure exhaustive handling:
const validators = {
string: (v: unknown): v is string => typeof v === 'string',
number: (v: unknown): v is number => typeof v === 'number',
boolean: (v: unknown): v is boolean => typeof v === 'boolean',
array: (v: unknown): v is unknown[] => Array.isArray(v),
} as const;
type ValidatorType = keyof typeof validators;
function validate(value: unknown, type: ValidatorType): boolean {
return validators[type](value);
}
validate('hello', 'string'); // valid
validate(42, 'number'); // valid
validate([], 'object'); // Error: 'object' not in ValidatorType
keyof With Mapped Types
keyof is a building block for mapped types that transform object shapes:
type Readonly<T> = {
readonly [K in keyof T]: T[K];
};
type Optional<T> = {
[K in keyof T]?: T[K];
};
type Nullable<T> = {
[K in keyof T]: T[K] | null;
};
type NullableUser = Nullable<User>;
// {
// id: string | null;
// name: string | null;
// email: string | null;
// age: number | null;
// }
Filtering Keys by Value Type
Use keyof with conditional types to extract keys whose values match a certain type:
type KeysOfType<T, V> = {
[K in keyof T]: T[K] extends V ? K : never;
}[keyof T];
type StringKeys = KeysOfType<User, string>;
// 'id' | 'name' | 'email'
type NumberKeys = KeysOfType<User, number>;
// 'age'
function getStringField(user: User, key: KeysOfType<User, string>): string {
return user[key];
}
getStringField(user, 'name'); // valid, returns string
getStringField(user, 'age'); // Error: 'age' is not a string key
keyof With Index Signatures
When an object type has an index signature, keyof includes the index type:
interface StringMap {
[key: string]: unknown;
length: number;
}
type StringMapKeys = keyof StringMap;
// string | number (because numeric keys are a subtype of string keys)
Be aware of this when working with dictionary-like types. Named properties are included alongside the index signature.
Wrap Up
The typeof operator bridges values and types, letting you derive a type from any expression without manual duplication. The keyof operator extracts property names as a union, enabling type-safe access patterns. Combined, they let you write code where types flow from your data, configuration objects become the source of truth, and the compiler catches invalid property access at build time rather than in production.
Related articles
- TypeScript tsconfig.json Deep Dive: Every Option Explained
A comprehensive guide to tsconfig.json covering compiler options, module resolution, path aliases, project references, and recommended configurations.
- TypeScript TypeScript Generics: Advanced Patterns and Constraints
Master advanced TypeScript generics patterns including conditional constraints, recursive types, generic factories, and type-safe builder patterns with examples.
- TypeScript TypeScript Type Guards and Narrowing Explained
Learn how TypeScript type guards and control flow narrowing work, including typeof, instanceof, in, discriminated unions, and custom type predicates.
- TypeScript TypeScript Utility Types: The Complete Guide
A comprehensive guide to all built-in TypeScript utility types including Partial, Required, Pick, Omit, Record, Extract, Exclude, and more with practical examples.