Question

TypeScript keyof: Safely Index Objects with Dynamic Keys

Question

When compiling TypeScript with --noImplicitAny, how can I safely access an object property using a dynamic string key without receiving this error?

Index signature of object type implicitly has an 'any' type

For example:

interface ISomeObject {
  firstKey: string;
  secondKey: string;
  thirdKey: string;
}

const someObject: ISomeObject = {
  firstKey: "firstValue",
  secondKey: "secondValue",
  thirdKey: "thirdValue"
};

const key: string = "secondKey";
const secondValue: string = someObject[key];

The key value comes from elsewhere in the application but is intended to be one of the object's valid keys. Is this possible while keeping --noImplicitAny enabled, and does casting the result to string solve the problem?

Short Answer

You will learn why a general string cannot safely index a specifically shaped TypeScript object, and how keyof describes the exact set of allowed property names. You will also see when an index signature or runtime validation is the correct solution.

Concept

A TypeScript object type can describe a fixed set of properties:

interface ISomeObject {
  firstKey: string;
  secondKey: string;
  thirdKey: string;
}

The valid property names are "firstKey", "secondKey", and "thirdKey". However, the type string means every possible string, including values such as "missingKey".

const key: string = "missingKey";

If TypeScript allowed someObject[key], it would have to decide the type of a property that might not exist. Older or less strict behavior could effectively treat that value as any, which bypasses type checking. With --noImplicitAny, TypeScript reports the problem instead.

Use keyof ISomeObject when a variable must contain one of the known property names:

const key: keyof ISomeObject = "secondKey";
const secondValue = someObject[key]; // string

keyof is important because it keeps dynamic access type-safe without giving up the benefits of strict compilation.

Mental Model

Think of someObject as a cabinet with exactly three labeled drawers: firstKey, secondKey, and thirdKey.

  • keyof ISomeObject is a key ring containing only keys that open those three drawers.
  • string is an unlimited box of labels. It might contain secondKey, but it could also contain unknownDrawer.

TypeScript lets you open the cabinet with a key from the known key ring. It refuses a completely unknown label unless the cabinet is designed to support arbitrary labels.

Take Quiz

Syntax and Examples

Use keyof to create a union of an object's property names.

interface ISomeObject {
  firstKey: string;
  secondKey: string;
  thirdKey: string;
}

const someObject: ISomeObject = {
  firstKey: "firstValue",
  secondKey: "secondValue",
  thirdKey: "thirdValue"
};

const key: keyof ISomeObject = "secondKey";
const value: string = someObject[key];

console.log(value); // secondValue

keyof ISomeObject becomes this union type:

type SomeObjectKey = "firstKey" | "secondKey" | "thirdKey";

You can also derive keys directly from an existing object:

const settings = {
  theme: "dark",
  language: "en"
};

type SettingsKey = keyof typeof settings;

const settingKey: SettingsKey = "theme";
const settingValue = settings[settingKey];

For reusable code, use a generic function. It accepts an object and only permits keys that belong to that object:

function getProperty<T, K extends keyof T>(object: T, key: K): T[K] {
  return object[key];
}

const value = getProperty(someObject, "thirdKey");
// value is string

T[K] means “the type of property K on object type T.”

Step by Step Execution

Consider this example:

interface User {
  id: number;
  name: string;
}

const user: User = {
  id: 42,
  name: "Ada"
};

const field: keyof User = "name";
const result = user[field];
  1. User declares two properties: id and name.
  2. keyof User evaluates to "id" | "name".
  3. field is assigned "name", which is allowed by that union.
  4. user[field] reads user["name"] at runtime.
  5. The result is "Ada".
  6. TypeScript infers result as string | number, because field is typed as either valid key and either property could be selected.

When the key is known as a specific literal, TypeScript can be more precise:

const nameField = "name" as const;
const name = user[nameField]; // string

If you try an invalid key, TypeScript stops you before the program runs:

const invalidField: keyof User = "email";
// Error: Type '"email"' is not assignable to type 'keyof User'.

Real World Use Cases

  • Sorting tables: Restrict a sort field to real fields such as "name", "createdAt", or "status".
  • Form handling: Read a known form field from a typed form-state object.
  • Configuration access: Retrieve values such as "apiUrl" and "retryCount" without misspelled setting names.
  • API data transformations: Select fields from a known response shape while preserving each field's value type.
  • UI rendering: Map a column configuration's key to a property on each row object.

Example: typed table columns:

interface Product {
  name: string;
  price: number;
}

const column: keyof Product = "price";
const product: Product = { name: "Notebook", price: 8.5 };

console.log(product[column]); // 8.5
Take Quiz

Real Codebase Usage

In real projects, values from users, URLs, JSON, environment variables, and external APIs usually arrive as plain string. Do not simply claim that such a value is keyof with a type assertion unless you trust its source.

A common pattern is runtime validation with a type guard:

interface ISomeObject {
  firstKey: string;
  secondKey: string;
  thirdKey: string;
}

const someObject: ISomeObject = {
  firstKey: "firstValue",
  secondKey: "secondValue",
  thirdKey: "thirdValue"
};

function isSomeObjectKey(key: string): key is keyof ISomeObject {
  return key in someObject;
}

function readValue(keyFromRequest: string): string | undefined {
  if (!isSomeObjectKey(keyFromRequest)) {
    return undefined;
  }

  return someObject[keyFromRequest];
}

The early return is a guard clause. After it runs, TypeScript knows the key is valid.

For dictionaries whose keys are genuinely unknown in advance, model them as a dictionary instead:

const scores: Record<string, number> = {
  alice: 10,
  bob: 15
};

const playerName: string = "alice";
const score = scores[playerName];

Consider enabling noUncheckedIndexedAccess for dictionary-heavy code. It makes potentially absent indexed values include undefined, encouraging a presence check.

Common Mistakes

Using string for a fixed object key

const key: string = "secondKey";
const value = someObject[key]; // Error

A string might be any text, not only a valid key. Use keyof ISomeObject when the key is known to be one of the object's fields.

Casting the value instead of fixing the key

const value = someObject[key] as string;

This does not prove that key is valid. If the key is wrong, the runtime value is undefined, even though you told TypeScript it was a string. Type the key or validate it first.

Casting an untrusted key without validation

const keyFromUrl = "notARealKey";
const value = someObject[keyFromUrl as keyof ISomeObject];

This silences the compiler but can hide runtime bugs. Use a type guard when the value comes from outside your typed code.

Adding an index signature to a fixed shape unnecessarily

interface ISomeObject {
  [key: string]: string;
  firstKey: string;
  secondKey: string;
  thirdKey: string;
}

This permits every string key, which may be correct for a dictionary but removes protection against misspelled fixed keys. Prefer keyof for a fixed set of properties.

Forgetting that property types can differ

interface User {
  name: string;
  age: number;
}

const field: keyof User = "name";
const value: string = ({ name: "Ada", age: 42 } as User)[field];
// Not safe: field could be "age".

With a variable typed as keyof User, the accessed value can be string | number. Preserve the inferred type, narrow the key, or use a generic function.

Comparisons

SituationBest type or approachWhy
A key must be one of an object's declared propertieskeyof TPrevents invalid property names.
A function reads a property and should preserve its exact value typeK extends keyof T and T[K]Connects the chosen key to its result type.
Keys are arbitrary text and values share one typeRecord<string, Value>Models a dictionary or lookup table.
A key comes from a URL, form, or APIRuntime validation plus a type guardTypeScript types do not validate runtime input.
Only a few literal keys are allowedA string-literal unionUseful when there is no object type to derive keys from.

keyof and an index signature solve different problems:

// Fixed and known property names
interface Profile {
  name: string;
  city: string;
}

type ProfileKey = keyof Profile; // "name" | "city"

// Arbitrary property names, with consistent value types
interface Labels {
  [key: string]: string;
}
Take Quiz

Cheat Sheet

// Get an object's valid key names
type Key = keyof ObjectType;

// Safe dynamic access
const key: keyof ObjectType = "knownKey";
const value = object[key];

// Derive keys from a value
type KeyFromValue = keyof typeof object;

// Generic safe getter
function getProperty<T, K extends keyof T>(object: T, key: K): T[K] {
  return object[key];
}

// Dictionary with arbitrary string keys
const dictionary: Record<string, number> = {};
const value = dictionary["any-key"];

// Validate a runtime string before indexing
function isKey(key: string): key is keyof typeof object {
  return key in object;
}

Rules:

  • Use keyof for objects with a known, fixed set of keys.
  • string is wider than keyof SomeObject.
  • Type assertions such as as keyof T do not perform runtime validation.
  • Use Record<string, T> or an index signature only when arbitrary keys are intentional.
  • If property values have different types, object[keyof ObjectType] may be a union of those types.

FAQ

Why does TypeScript reject object[key] when key is a string?

Because string could be a property name that does not exist on the object. The object type only guarantees its declared keys.

How do I get all keys of a TypeScript interface?

Use keyof:

type Keys = keyof ISomeObject;

Does as string fix the implicit any index error?

No. It only asserts the expected result type and does not establish that the key is valid. Type or validate the key instead.

What if the key comes from user input or an API response?

Treat it as string, check it at runtime, then use a type guard to narrow it to keyof YourType.

When should I use Record<string, string> instead of keyof?

Use Record<string, string> when any string may be a valid key and all values are strings. Use keyof when keys are known and limited.

Why is the result sometimes a union such as string | number?

If a key can refer to properties with different value types, TypeScript must include all possible value types. A generic getter can preserve the exact type for a specific key.

Can I keep noImplicitAny enabled?

Yes. keyof, generic constraints, index signatures, and runtime validation are designed to model dynamic access without disabling strict type checking.

Take Quiz

Mini Project

Description

Build a small typed preferences reader. The application has a fixed set of preferences, while a command or request may provide a preference name as a plain string. The reader must return the selected value only after confirming that the name is valid.

Goal

Create a function that safely reads a preference from a runtime string without using any or unsafe key assertions.

Requirements

  • Define a Preferences interface with at least three properties.
  • Create a preferences object that matches the interface.
  • Accept a requested preference name as a string.
  • Validate whether the requested name is a real preference key.
  • Return the preference value for valid keys and undefined for invalid keys.
  • Keep the solution compatible with noImplicitAny.
Take Quiz

Keep learning

Related questions

View more