TypeScript keyof typeof — Complete Guide
In this tutorial, you will learn about TypeScript keyof typeof. We cover key concepts, practical examples, and best practices to help you master this topic.
TypeScript's keyof and typeof operators bridge the gap between values and types — keyof extracts object keys as a union, typeof captures the type of a runtime value, and indexed access types drill into specific property types.
What You'll Learn
- The
keyofoperator for object key unions - The
typeofoperator for runtime value types - Indexed access types (
T[K]) - Combining keyof + typeof for enum-like patterns
- Practical patterns for type-safe property access
Why It Matters
These operators let you derive types from existing code rather than duplicating them. When you change an object's shape, keyof and typeof ensure all dependent types update automatically. This is the foundation of TypeScript's "type from value" philosophy.
Real-World Use
The Doda Browser API uses keyof to type-safe event names — keyof typeof browser.events produces a union of all valid event names, preventing typos in addListener calls. Indexed access types extract the parameter types for each event.
Learning Path
flowchart LR A[Advanced Generics] --> B[keyof typeof] B --> C[Template Literal Types] B --> D[You Are Here] C --> E[Conditional Types] E --> F[Mapped Types]
The keyof Operator
keyof takes an object type and returns a union of its property names (as string or number literals):
interface User {
name: string;
age: number;
email: string;
}
type UserKeys = keyof User;
// "name" | "age" | "email"
const key1: UserKeys = "name";
const key2: UserKeys = "age";
// const key3: UserKeys = "ssn"; // Error
Think of keyof as the "keys of" operator — it lists all valid property names of a type.
keyof with Index Signatures
interface Dictionary {
[key: string]: unknown;
}
type DictKeys = keyof Dictionary;
// string | number
// (JavaScript object keys are always stringified, but TypeScript includes number for array-like access)
keyof with Arrays
type ArrayKeys = keyof [string, number];
// "0" | "1" | "length" | "push" | "pop" | ... (all Array methods)
The typeof Operator
typeof captures the type of a runtime value:
const user = {
name: "Alice",
age: 30,
email: "alice@example.com",
};
type UserType = typeof user;
// { name: string; age: number; email: string }
const anotherUser: UserType = {
name: "Bob",
age: 25,
email: "bob@example.com",
};
typeof with Primitives
const name = "Alice";
type T = typeof name; // string
const age = 30;
type A = typeof age; // number
const isActive = true;
type B = typeof isActive; // boolean
const sym = Symbol("id");
type S = typeof sym; // symbol
typeof with Functions
function greet(name: string): string {
return `Hello, ${name}`;
}
type GreetFn = typeof greet;
// (name: string) => string
Combining keyof and typeof
The classic pattern: derive keys from a runtime object's type:
const Colors = {
Red: "#ff0000",
Green: "#00ff00",
Blue: "#0000ff",
} as const;
type ColorName = keyof typeof Colors;
// "Red" | "Green" | "Blue"
type ColorValue = typeof Colors[ColorName];
// "#ff0000" | "#00ff00" | "#0000ff"
function getColor(name: ColorName): ColorValue {
return Colors[name];
}
console.log(getColor("Red")); // #ff0000
This pattern is superior to enums in many cases — you get string literal types with zero runtime overhead (just a plain object).
Indexed Access Types
You can access a property type using bracket syntax, just like accessing a property value:
interface User {
name: string;
age: number;
address: {
street: string;
city: string;
zip: string;
};
tags: string[];
}
type NameType = User["name"]; // string
type AgeType = User["age"]; // number
type AddressType = User["address"]; // { street: string; city: string; zip: string }
type TagsType = User["tags"]; // string[]
Accessing Union Keys
type UserPropertyTypes = User["name" | "age"];
// string | number
type AllValues = User[keyof User];
// string | number | { street: string; city: string; zip: string } | string[]
Array Element Access
const items = [{ id: 1, name: "A" }, { id: 2, name: "B" }];
type ItemType = typeof items[number];
// { id: number; name: string }
type IdType = typeof items[number]["id"];
// number
Practical Patterns
Type-Safe Object Lookup
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { name: "Alice", age: 30, active: true };
const name = getProperty(user, "name"); // string
const age = getProperty(user, "age"); // number
Dynamic Object Builder
function createObject<T>() {
return {
add<K extends keyof T>(key: K, value: T[K]): void {
// ...
},
};
}
interface Config {
host: string;
port: number;
debug: boolean;
}
const builder = createObject<Config>();
// builder.add("host", "localhost"); // OK
// builder.add("host", 123); // Error
Mapping Object Values
function mapValues<T, R>(
obj: T,
fn: (value: T[keyof T], key: keyof T) => R
): { [K in keyof T]: R } {
const result = {} as { [K in keyof T]: R };
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
result[key] = fn(obj[key], key);
}
}
return result;
}
const nums = { a: 1, b: 2, c: 3 };
const doubled = mapValues(nums, (x) => x * 2);
// { a: number, b: number, c: number }
console.log(doubled.a); // 2
Common Mistakes
1. Confusing JavaScript typeof with TypeScript typeof
// Runtime (JavaScript) typeof
console.log(typeof "hello"); // "string"
// Compile-time (TypeScript) typeof
type T = typeof "hello"; // string
In a type position, typeof captures the TypeScript type. In a value position, it's JavaScript's runtime operator.
2. Using keyof on a Union of Objects
type A = { x: number };
type B = { y: string };
type Union = A | B;
type Keys = keyof Union; // never — no common keys
// Use intersection instead:
type Intersection = A & B;
type Keys2 = keyof Intersection; // "x" | "y"
3. Forgetting as const with Object Literals
const config = { mode: "production", port: 3000 };
type ConfigType = typeof config;
// { mode: string; port: number } — widened!
const config2 = { mode: "production", port: 3000 } as const;
type ConfigType2 = typeof config2;
// { readonly mode: "production"; readonly port: 3000 } — literal!
4. Indexing with a Type That Doesn't Exist on the Object
interface User { name: string; }
// type N = User["ssn"]; // Error: Property 'ssn' does not exist
5. Using keyof on a Primitive
type K = keyof string;
// number | typeof Symbol.iterator | "toString" | "charAt" | "concat" | ...
// (all string prototype methods — usually not what you want)
Practice Questions
What does
keyof Tproduce? A union of all property keys of typeT.What is the difference between JavaScript
typeofand TypeScripttypeof? JavaScripttypeofis a runtime operator that returns a string. TypeScripttypeofis a compile-time operator that captures the static type of a value.How do you combine
keyofandtypeofto get keys from a runtime object?keyof typeof myObject— first capture the object's type, then extract its keys.What does
T[K]represent in an indexed access type? The type of propertyKon typeT.
Challenge: Given a const object const API_ENDPOINTS = { users: "/api/users", scans: "/api/scans", threats: "/api/threats" } as const;, use keyof typeof to create a typed function callApi(endpoint) that only accepts valid endpoint names and returns the correct URL.
FAQ
Mini Project: Type-Safe Configuration Loader
// src/config-loader.ts
const DEFAULT_CONFIG = {
app: {
name: "DodaTech Scanner",
version: "2.1.0",
debug: false,
},
database: {
host: "localhost",
port: 5432,
user: "admin",
password: "",
},
features: {
realTimeScanning: true,
autoUpdate: true,
maxFileSize: 10485760,
},
} as const;
type Config = typeof DEFAULT_CONFIG;
type ConfigSection = keyof Config;
function getConfigSection<S extends ConfigSection>(section: S): Config[S] {
return DEFAULT_CONFIG[section];
}
function getConfigValue<
S extends ConfigSection,
K extends keyof Config[S]
>(section: S, key: K): Config[S][K] {
return DEFAULT_CONFIG[section][key];
}
// Usage
const appSection = getConfigSection("app");
// Type: { readonly name: "DodaTech Scanner"; readonly version: "2.1.0"; readonly debug: false }
const dbHost = getConfigValue("database", "host");
// Type: "localhost"
const debug = getConfigValue("app", "debug");
// Type: false
console.log(`${appSection.name} v${appSection.version}`);
console.log(`Database at ${dbHost}`);
console.log(`Debug mode: ${debug}`);
Expected output:
DodaTech Scanner v2.1.0
Database at localhost
Debug mode: false
What's Next
Now explore template literal types for string manipulation at the type level:
| Lesson | Description |
|---|---|
| {{< ref "/programming-languages/typescript/10-generics-advanced" >}} | Review advanced generics |
| {{< ref "/programming-languages/typescript/12-template-literal-types" >}} | Template literal and intrinsic string types |
| {{< ref "/programming-languages/typescript/13-conditional-types" >}} | Conditional types deep dive |
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro