Skip to content
ExponaBLOG
JAVASCRIPT & TYPESCRIPT · DAY 045 / 100

keyof and typeof for JavaScript & TypeScript developers

Derive types from values and objects.

Expona Admin8 min read

keyof and typeof: Deriving Types from Values and Objects

If you've written TypeScript long enough, you've hit a moment where you wished you could extract the shape of an object or the keys available on it—without writing it out manually. That's where keyof and typeof come in. These two operators let you derive types from values and objects, creating a single source of truth that keeps your codebase maintainable and your types in sync with reality.

Why We Need Type Derivation

Imagine you have a configuration object that your application reads at runtime. You define it as a plain JavaScript object, but now you need TypeScript to know what keys and values are valid when other parts of your code interact with it. You could manually write out the type, but that creates duplication. Every time your config changes, you'd have to update both the object and the type. With keyof and typeof, you define the object once and derive the type from it. This is the foundation of the single source of truth pattern.

typeof Turns a Value Into a Type

The typeof operator in TypeScript does something different than the runtime typeof you might know from JavaScript. In TypeScript's type system, typeof extracts the type of a value or expression and lets you use it as a type annotation elsewhere.

typescript
const user = {
  id: 1,
  name: "Alice",
  email: "alice@example.com",
  isActive: true
};

type User = typeof user;

// User is now: {
//   id: number;
//   name: string;
//   email: string;
//   isActive: boolean;
// }

const anotherUser: User = {
  id: 2,
  name: "Bob",
  email: "bob@example.com",
  isActive: false
};

This is powerful because you're not duplicating the shape. The type follows the value. If someone later modifies the user object to add a role field, every place that uses User automatically knows about it.

You can also use typeof on functions and classes:

typescript
function calculateTotal(items: number[]): number {
  return items.reduce((sum, item) => sum + item, 0);
}

type CalculateTotal = typeof calculateTotal;
// type CalculateTotal = (items: number[]) => number

class Logger {
  log(message: string): void {
    console.log(message);
  }
}

type LoggerType = typeof Logger;
// Captures the constructor signature and static methods

keyof Gets a Union of Keys

The keyof operator creates a union type of all the keys in an object type. It's your way of asking TypeScript, "What are all the valid property names on this type?"

typescript
type Config = {
  apiUrl: string;
  timeout: number;
  retryCount: number;
  logLevel: "debug" | "info" | "error";
};

type ConfigKeys = keyof Config;
// ConfigKeys is: "apiUrl" | "timeout" | "retryCount" | "logLevel"

const key: ConfigKeys = "apiUrl"; // Valid
const invalidKey: ConfigKeys = "unknownKey"; // Error

This is invaluable when you're writing generic functions that need to respect the shape of an object:

typescript
function getProperty(obj: T, key: K): T[K] {
  return obj[key];
}

const user = { name: "Charlie", age: 30 };
const name = getProperty(user, "name"); // name is string
const age = getProperty(user, "age");   // age is number

// getProperty(user, "unknown"); // Error: not a valid key

Combine: keyof typeof config

Now for the magic. Combine typeof and keyof, and you can extract the union of keys directly from a value. This pattern is especially useful when you have a configuration object or a constants object that you want to type-guard against.

typescript
const appConfig = {
  env: "production",
  port: 3000,
  database: "postgres",
  features: {
    darkMode: true,
    betaApi: false
  }
};

type ConfigKeys = keyof typeof appConfig;
// ConfigKeys is: "env" | "port" | "database" | "features"

function getConfig(key: ConfigKeys) {
  return appConfig[key];
}

getConfig("env");      // OK
getConfig("port");     // OK
getConfig("unknown");  // Error: not a valid config key

This is a real-world pattern I've used in production. Let me show you a practical example from a ride-hailing platform:

typescript
const featureFlags = {
  enablePricing: true,
  enableRatings: true,
  enableScheduledRides: false,
  enableSOS: true,
  enableSharedRides: false
} as const;

type FeatureFlag = keyof typeof featureFlags;

function isFeatureEnabled(flag: FeatureFlag): boolean {
  return featureFlags[flag];
}

// Usage
if (isFeatureEnabled("enablePricing")) {
  // Show pricing UI
}

if (isFeatureEnabled("unknownFlag")) {
  // Error at compile time
}

Notice the as const assertion. This tells TypeScript to treat the object values as literal types, not just boolean. Without it, keyof still works, but the pattern becomes more valuable with const assertions because you preserve the exact literal values.

Single Source of Truth for Constants

In production systems, you often have constants that define valid values or keys. Instead of maintaining a separate type and a separate object, derive the type from the object. This keeps them in sync and makes refactoring safer.

typescript
const orderStatuses = {
  PENDING: "pending",
  PROCESSING: "processing",
  SHIPPED: "shipped",
  DELIVERED: "delivered",
  CANCELLED: "cancelled"
} as const;

type OrderStatus = typeof orderStatuses[keyof typeof orderStatuses];
// OrderStatus is: "pending" | "processing" | "shipped" | "delivered" | "cancelled"

type OrderStatusKey = keyof typeof orderStatuses;
// OrderStatusKey is: "PENDING" | "PROCESSING" | "SHIPPED" | "DELIVERED" | "CANCELLED"

interface Order {
  id: string;
  status: OrderStatus;
}

function updateOrderStatus(orderId: string, status: OrderStatus) {
  // API call
}

updateOrderStatus("123", "delivered"); // OK
updateOrderStatus("123", "invalid");    // Error

I've used this pattern extensively in a CRM system where we had dozens of status enums. Every time a status was added or removed, the types automatically updated. No more mismatches between the backend enum and the frontend type.

Practical Scenarios in Production

Consider an e-commerce application with a product filter:

typescript
const filterOptions = {
  category: ["electronics", "clothing", "home"],
  priceRange: ["0-50", "50-100", "100-500"],
  rating: ["1+", "2+", "3+", "4+", "5"],
  inStock: [true, false]
} as const;

type FilterKey = keyof typeof filterOptions;

function getFilterOptions(filterKey: FilterKey) {
  return filterOptions[filterKey];
}

// In a React component
const categories = getFilterOptions("category");
// categories is readonly ["electronics", "clothing", "home"]

This ensures that your UI can only request filters that actually exist, caught at compile time.

Common Mistakes

  • Forgetting as const: Without it, keyof typeof still works, but the values are inferred as their base types (all strings become string, not literal types). You lose precision.
    typescript
    // Without as const
    const config = { mode: "dev" }; // mode is string
    type Mode = typeof config.mode; // string, not "dev"
    
    // With as const
    const config = { mode: "dev" } as const;
    type Mode = typeof config.mode; // "dev"
    
  • Using keyof on a type instead of a value: If you've already defined a type, typeof won't help. keyof works on types directly, but the pattern only shines when you derive from a value.
    typescript
    type User = { name: string; age: number };
    type UserKeys = keyof User; // Works, but defeats the "single source of truth" goal
    
  • Not considering readonly with as const: When you use as const, arrays and objects become readonly. This is usually what you want for constants, but it can surprise you if you try to mutate the object later.
  • Over-nesting keyof typeof: In complex scenarios, chaining too many operations can make code hard to read. Consider breaking it into intermediate types for clarity.

Key Takeaways

  • typeof in TypeScript extracts the type of a value, creating a bridge between runtime values and the type system.
  • keyof creates a union of all keys in a type, useful for constraining which properties you can access.
  • Combined, keyof typeof lets you derive a union of keys directly from an object value, enabling single source of truth patterns.
  • Use as const on objects and arrays to preserve literal types and unlock the full power of type derivation.
  • This pattern is especially powerful for configuration objects, feature flags, and status enums where keeping types in sync with values matters.
  • It reduces boilerplate, catches refactoring errors at compile time, and makes your code more maintainable.
ShareXLinkedInReddit

Found this useful? Support the writing.

Buy me a coffee
Written by
Expona Admin
Site administrator

Comments

Loading comments…

Leave a comment

Comments are reviewed before they appear. Be kind and stay on topic.

Related articles