keyof, typeof & Indexed Access Types: TypeScript's Type-Level Toolkit
The keyof operator, the typeof type operator, and indexed access types are the
three tools that let you derive TypeScript types from values and other types instead of
hand-writing them twice. Learn them together and a whole class of "the type drifted out of
sync with the object" bugs disappears — because the types are computed from the source of
truth, not maintained in parallel. This guide sits in the Devgains
TypeScript cluster next to
generics and
as const, which are the two
features these operators pair with most often.
Quick answer: what do these three operators do?
keyof Ttakes a type and produces a union of its property keys as string (or number/symbol) literal types.typeof value(in a type position) takes a runtime value and produces its type.T[K]— an indexed access type — takes a typeTand a keyKand produces the type of that property.
Chained together they read like a sentence: "the type of the values in this object" is
(typeof obj)[keyof typeof obj]. That one expression is the backbone of type-safe config
objects, enums-from-objects, and lookup tables.
Why derive types instead of writing them?
Every time you write a type by hand that mirrors a value, you create two things that must be kept in sync manually. Add a key to the object, forget to update the type, and you have a silent bug the compiler can't catch because you told it the wrong shape.
Deriving types inverts that. The value is the single source of truth, and the type is computed from it. Add a key and every derived type updates automatically. This is the same DRY instinct behind type-safe API calls without a code generator — push the compiler to infer structure rather than restate it.
keyof: turning an object type into a union of keys
keyof reads the keys of a type and gives you their union:
interface User {
id: number;
name: string;
email: string;
}
type UserKey = keyof User; // "id" | "name" | "email"That union is the raw material for safe property access. A generic "get a property" helper that only accepts real keys looks like this:
function getProp<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user: User = { id: 1, name: "Ada", email: "[email protected]" };
const name = getProp(user, "name"); // typed as string
const id = getProp(user, "id"); // typed as number
getProp(user, "phone"); // compile error: "phone" is not a key of UserNotice the return type T[K] — that's an indexed access type, and it's why name is
string and id is number rather than a vague string | number. The generic
constraint K extends keyof T is one of the most common patterns in real TypeScript; see
generics that don't make people cry for how
constraints like this work.
typeof: turning a value into a type
In a type position, typeof captures the inferred type of a value. It's most powerful
combined with as const, which freezes an object into deeply readonly literal types:
const config = {
env: "production",
retries: 3,
featureFlags: ["search", "billing"],
} as const;
type Config = typeof config;
// {
// readonly env: "production";
// readonly retries: 3;
// readonly featureFlags: readonly ["search", "billing"];
// }Without as const, env would widen to string and retries to number. With it, you
keep the exact literals — which is exactly what makes the derivations below precise. The
as const guide covers that widening
behavior in depth.
Indexed access types: T[K]
An indexed access type looks up the type of a property. It works with a single key, a union
of keys, or the special number index for arrays:
type Env = Config["env"]; // "production"
type Flags = Config["featureFlags"]; // readonly ["search", "billing"]
// A union of keys yields a union of value types:
type Values = User[keyof User]; // number | string
// number indexes into an array/tuple element type:
const roles = ["admin", "editor", "viewer"] as const;
type Role = (typeof roles)[number]; // "admin" | "editor" | "viewer"That last pattern — (typeof arr)[number] — is the idiomatic way to build a union type
from a runtime array. Define the list once, derive the type from it, and the two can never
disagree.
Step-by-step: a type-safe enum from an object
Here's the pattern that ties all three together. You want an object of constants and a type that is exactly its set of values — no duplication.
1. Define the object with as const:
const HttpStatus = {
OK: 200,
NotFound: 404,
ServerError: 500,
} as const;2. Derive the value type with typeof + indexed access:
type HttpStatusCode = (typeof HttpStatus)[keyof typeof HttpStatus];
// 200 | 404 | 5003. Use it — and watch it stay in sync:
function handle(code: HttpStatusCode) {
/* ... */
}
handle(HttpStatus.NotFound); // ok
handle(418); // error: 418 is not 200 | 404 | 500Add Teapot: 418 to HttpStatus and HttpStatusCode automatically becomes
200 | 404 | 500 | 418. No second edit, no drift. This is the object-based alternative to
enum that many teams prefer because it produces no extra runtime code and plays nicely
with discriminated unions.
keyof vs typeof at a glance
They're easy to confuse because they often appear together. The distinction is what they consume and produce:
| Operator | Input | Output | Example |
|---|---|---|---|
keyof T | a type | union of its keys | keyof User → "id" | "name" |
typeof v | a value | the type of that value | typeof config → Config |
T[K] | type + key | the type of that property | User["id"] → number |
The mental model: typeof crosses from the value world into the type world; keyof and
indexed access operate entirely inside the type world once you're there.
Best practices
- Derive, don't duplicate. If a type mirrors a value's shape, compute it with
typeofinstead of writing it by hand. - Reach for
as constfirst. Mosttypeof-based derivations only produce precise literal types when the value isas const. - Use
(typeof arr)[number]for value unions. One array as the source of truth, a derived union that can't drift. - Constrain generics with
K extends keyof T. It's the safe, reusable shape for any property-access helper. - Prefer indexed access over restating property types.
T["field"]stays correct whenTchanges; a hand-copied type does not.
Common mistakes
- Forgetting
as const, then wondering why literals widened.typeof configgives youstring/numberwithout it — see theas constarticle. - Confusing runtime
typeofwith type-leveltypeof.typeof x === "string"is a runtime check;type T = typeof xis a compile-time type query. Same keyword, different worlds. - Using
keyofon a type with an index signature.keyof { [k: string]: number }isstring | number, not a nice literal union — that's expected, not a bug. - Over-deriving until nobody can read it. Deeply nested
T[K1][K2][number]chains are powerful but can slow the compiler and hurt readability, as covered in why your types slow down the editor.
Takeaways
keyofturns a type into a union of its keys;typeofturns a value into its type;T[K]looks up a property's type.- Chained as
(typeof obj)[keyof typeof obj], they derive a value-union type from any constant object. - Pair them with
as constto preserve literal types and keep types in lockstep with data. - Deriving types eliminates the drift bugs you get from maintaining a type and a value by hand.
FAQ
What does keyof do in TypeScript?
keyof takes a type and produces a union of its property keys as literal types. For an
interface with keys id, name, and email, keyof yields "id" | "name" | "email",
which you can then use to constrain generics or index into the type.
What is the difference between keyof and typeof?
keyof operates on a type and returns the union of its keys. typeof (in a type
position) operates on a value and returns that value's type. They're frequently
combined as keyof typeof value to get the keys of an existing object.
What is an indexed access type?
An indexed access type, written T[K], looks up the type of a property. User["id"]
resolves to number. Using a union of keys, like User[keyof User], produces a union of
all the property value types.
How do I get a union type from an array in TypeScript?
Declare the array with as const and index it by number: (typeof roles)[number]. This
derives a union of the array's element types that stays in sync with the array
automatically.
Conclusion
keyof, typeof, and indexed access types are small operators with an outsized payoff:
they let the compiler compute your types from the values you already have, so the two can
never drift apart. Start by adding as const to your constant objects, derive value unions
with (typeof x)[keyof typeof x], and constrain your helper generics with keyof. From
there, the rest of the TypeScript cluster — from
generics to the
satisfies operator — builds on exactly
these primitives.
References
- TypeScript Handbook — Keyof Type Operator
- TypeScript Handbook — Typeof Type Operator
- TypeScript Handbook — Indexed Access Types
- TypeScript Handbook — Everyday Types &
as const

