Skip to content

TypeScript keyof typeof — Complete Guide

DodaTech Updated 2026-06-28 8 min read

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 keyof operator for object key unions
  • The typeof operator 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

  1. What does keyof T produce? A union of all property keys of type T.

  2. What is the difference between JavaScript typeof and TypeScript typeof? JavaScript typeof is a runtime operator that returns a string. TypeScript typeof is a compile-time operator that captures the static type of a value.

  3. How do you combine keyof and typeof to get keys from a runtime object? keyof typeof myObject — first capture the object's type, then extract its keys.

  4. What does T[K] represent in an indexed access type? The type of property K on type T.

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

Can I use `keyof` with any type?

Yes. keyof works with any type. For primitives, it returns the Prototype methods. For objects, it returns the property keys.

What is the difference between `keyof T` and `keyof typeof T`?

If T is a type, keyof T is sufficient. If T is a value (variable), use keyof typeof T to first get its type.

Can I use `typeof` to get the type of a class?

Yes: type MyClassType = typeof MyClass; gives the constructor type (with new and prototype).

What happens with `keyof` on an array type?

It returns all array property names and method names: "length", "push", "pop", "0", "1", etc.

Can indexed access types be nested?

Yes: T[K][V] accesses nested property types.

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