Reading Types From Values: keyof, typeof, and Indexed Access
Learn to derive new types from existing types and values using the keyof operator, the typeof type operator, and indexed access types, so reusable utilities stay in sync with the data they describe.
Instead of hand-writing a second type every time a shape changes, TypeScript lets you pull types directly out of other types and even out of runtime values. The keyof operator gives you a union of property names, typeof lifts a value's shape into the type system, and indexed access lets you reach in and grab the type of a single property. Together these three operators are the backbone of almost every generic utility you'll write.
keyof: turning an object type into a union of its keys
The keyof operator takes an object type and produces a union type made up of that object's property names as string (or number/symbol) literal types. Given interface User { id: number; name: string; }, keyof User evaluates to the type "id" | "name" — not the values, but the literal names themselves, usable anywhere a type is expected.
This becomes powerful the moment you combine it with a generic function. A function like function getProp<T, K extends keyof T>(obj: T, key: K): T[K] can only be called with a key that actually exists on T, and TypeScript infers the correct return type automatically. Without keyof, you would have to accept key: string and lose all type safety, letting typos like "nam" slip through silently.
keyof also works on classes, interfaces, and type aliases alike, and it respects modifiers — a readonly or optional property is still included in the resulting union, since keyof only cares about the property name, not how it's declared.
Indexed access types: T["key"]
Indexed access syntax lets you look up the type of a specific property using bracket notation at the type level, mirroring how you'd access a property at runtime. User["name"] evaluates to string, exactly the declared type of the name property — TypeScript is reading the shape of User, not executing any code.
You aren't limited to a single literal key. Passing a union of keys returns a union of the corresponding value types: User["id" | "name"] evaluates to number | string. Passing keyof User itself returns a union of every property's value type, which is exactly how the built-in utility type T[keyof T] is used internally to get 'any value type on this object'.
Indexed access also works on arrays and tuples in a useful way: Widget[number] gives you the element type of an array of widgets, which is the standard trick for extracting an item type from an array type when no named type exists for a single element.
typeof: lifting a value's shape into a type
The typeof type operator (distinct from JavaScript's runtime typeof, though it shares the keyword) takes a value and produces the type TypeScript has inferred for it. Given const user = { id: 1, name: "Ada" };, writing typeof user as a type produces { id: number; name: string; } — the same shape TypeScript already inferred when you declared user.
This is most valuable when the value, not a hand-written interface, is the source of truth — configuration objects, default state, or constants built with as const. Rather than maintaining a type and a value that describe the same shape and letting them drift apart, you declare the value once and derive the type from it with typeof.
Combining typeof with keyof is an extremely common pattern: keyof typeof someObject gives you a union of that object's keys without ever writing an interface for it, which is especially handy for things like a colors object, a routes map, or an enum-like const object.
keyof, typeof, and indexed access together
keyof only exists at the type level — it disappears entirely when compiled to JavaScript. typeof (the type operator) can only be used where a type is expected, e.g. after a colon or inside angle brackets, never as a runtime expression. Indexed access with a key that doesn't exist on the type is a compile error, catching typos before they ship.
interface User {
id: number;
name: string;
isAdmin: boolean;
}
type Keys = keyof User;
// "id" | "name" | "isAdmin"
type NameType = User["name"];
// string
const user = { id: 1, name: "Ada", isAdmin: false };
type RuntimeType = typeof user;
// { id: number; name: string; isAdmin: boolean }
function getProp<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}Code Examples
ProductKey is a union of the literal property names of Product. The describe function can only be called with one of those exact strings, so a typo like "weight" is caught at compile time instead of failing silently at runtime. Expected result: Compile error on describe("weight"): Argument of type '"weight"' is not assignable to parameter of type 'ProductKey'.
type ProductKey = keyof Product;
Rather than writing a separate Theme interface, the type is derived directly from the defaultTheme object with typeof, and ThemeKey is derived from that with keyof. applyTheme then uses T[K]-style indexed access (through the generic K) to demand a value of the correct type for whichever key is passed.
type Theme = typeof defaultTheme;
Common mistake: Writing a hand-maintained Theme interface alongside defaultTheme risks the two silently drifting apart as the object grows; deriving the type with typeof keeps a single source of truth.
interface Product {
sku: string;
price: number;
inStock: boolean;
}
type ProductKey = keyof Product;
// "sku" | "price" | "inStock"
function describe(key: ProductKey): string {
return `Product has a field called ${key}`;
}
describe("price"); // OK
describe("weight"); // Error: not assignable to ProductKeyconst defaultTheme = {
primary: "#3366ff",
background: "#ffffff",
spacingUnit: 8
};
type Theme = typeof defaultTheme;
type ThemeKey = keyof Theme;
function applyTheme<K extends ThemeKey>(key: K, value: Theme[K]) {
console.log(`Setting ${key} to ${value}`);
}
applyTheme("primary", "#ff0000"); // OK, string expected
applyTheme("spacingUnit", 12); // OK, number expected
applyTheme("spacingUnit", "12px"); // Error: expected numberFrom value to type and back to a safe accessor
typeof lifts a value into a type; keyof extracts its property names; indexed access reaches back in to read a single property's type.
const user = { id: 1, name: "Ada" }
| typeof
v
type RuntimeType = { id: number; name: string }
| keyof
v
type Keys = "id" | "name"
| RuntimeType[K]
v
type of a single property, e.g. string for "name"Important Rules
keyof produces a union of a type's own property names (as literal string/number/symbol types), not their values.
Indexed access syntax T["key"] mirrors runtime property access but operates entirely on types, at compile time.
typeof (the type operator) converts a value into the type TypeScript inferred for it — use it to derive types from constants and configuration objects instead of duplicating them by hand.
keyof typeof someObject is the standard combination for deriving a union of keys directly from a plain object value with no separate interface.
Where this shows up
In React and similar UI code, keyof and indexed access are what make a generic <Table> or <Form> component type-safe: a column definition's field: keyof RowData ensures you can only reference a column that actually exists on the row type, and a getter typed as RowData[typeof field] guarantees the rendered value matches the column's real type. Configuration objects (themes, feature flags, API endpoint maps) are almost always typed with typeof config plus keyof typeof config so the type updates automatically whenever the object does, with zero duplication.
function getProp(obj: any, key: string) { return obj[key]; } function getProp<T, K extends keyof T>(obj: T, key: K): T[K] { return obj[key]; }
Typing key as a plain string discards all safety — any string compiles, even ones that don't exist on the object, and the return type collapses to any. Constraining K to keyof T restricts valid keys and preserves the exact return type.
type Keys = keyof user; // user is a value, not a type type Keys = keyof typeof user;
keyof expects a type, not a value. Passing a value directly is a type error; typeof must be used first to lift the value into a type before keyof can read its property names.
[multiple-choice] What does keyof User evaluate to for interface User { id: number; name: string; }? A. The values 1 and "Ada" B. The type "id" | "name" C. The type string[] D. An error, keyof only works on classes
B. The type "id" | "name" — keyof produces a union of a type's property names as literal types, here "id" | "name".
[predict-output] Given const p = { x: 1, y: 2 }; what does typeof p evaluate to? A. "object" B. { x: number; y: number } C. unknown D. any
B. { x: number; y: number } — The typeof type operator lifts the inferred shape of the value into a type, here an object type with x and y both typed as number.
[multiple-choice] Why does function get<T, K extends keyof T>(obj: T, key: K): T[K] return the exact property type instead of any? A. Because T[K] is an indexed access type that looks up the real type of the property named by K B. Because TypeScript guesses based on the property name C. It doesn't — the return type is always any D. Because K is always a number
A. Because T[K] is an indexed access type that looks up the real type of the property named by K — T[K] is an indexed access type: since K is constrained to keyof T, TypeScript can resolve exactly which property is being accessed and return its declared type.
Given const settings = { darkMode: true, fontSize: 14, language: "en" };, write a generic function updateSetting<K extends keyof typeof settings>(key: K, value: typeof settings[K]) => void that only accepts a valid settings key paired with a value of the matching type.
const settings = { darkMode: true, fontSize: 14, language: "en" }; function updateSetting<K extends keyof typeof settings>( key: K, value: typeof settings[K] ) { settings[key] = value; } updateSetting("fontSize", 16); // OK updateSetting("darkMode", "yes"); // Error: expected boolean
You'll need keyof typeof settings for the key constraint, and indexed access typeof settings[K] for the value type.