Question

Loop Through a String-Keyed Dictionary in TypeScript

typescriptjavascript

Question

How can I loop through the keys of a string-indexed dictionary in TypeScript? For example, given this dictionary type and value, how do I access each key (and optionally each value)?

const myDictionary: { [index: string]: any } = {};

Short Answer

You will learn how TypeScript represents string-keyed dictionaries and how to iterate through their keys, values, and key-value pairs. You will also see why Record<string, unknown> is usually safer than using any.

Concept

A dictionary is an object that stores values under named keys. In TypeScript, a string index signature describes an object whose property names are strings:

const scores: { [key: string]: number } = {
  alice: 10,
  bob: 20
};

The type means: “for every string key in this object, the associated value is a number.”

At runtime, dictionaries are ordinary JavaScript objects. Therefore, you can use standard object APIs to inspect them:

  • Object.keys(object) returns an array of keys.
  • Object.values(object) returns an array of values.
  • Object.entries(object) returns key-value pairs.
  • for...in visits enumerable property names.

The important TypeScript part is choosing an accurate value type. Avoid any when possible because it disables type checking. If values may have different, unknown shapes, use unknown instead.

Mental Model

Think of a dictionary as a labelled storage cabinet.

  • Each key is a label on a drawer, such as "alice" or "theme".
  • Each value is the item inside that drawer.
  • Object.keys() gives you a list of drawer labels.
  • Object.values() gives you the contents.
  • Object.entries() gives you both each label and its content together.

The index signature tells TypeScript what kind of item every drawer is allowed to contain.

Take Quiz

Syntax and Examples

Use Object.keys() when you only need dictionary keys.

const myDictionary: Record<string, string> = {
  firstName: "Ada",
  lastName: "Lovelace",
  role: "Programmer"
};

for (const key of Object.keys(myDictionary)) {
  console.log(key);
}

Output:

firstName
lastName
role

Access the value for a key with bracket notation:

for (const key of Object.keys(myDictionary)) {
  const value = myDictionary[key];
  console.log(`${key}: ${value}`);
}

When both the key and value are needed, Object.entries() is often clearer:

for (const [key, value] of Object.entries(myDictionary)) {
  console.log(`${key}: ${value}`);
}

A generic helper can preserve the dictionary value type:

function printDictionary<T>(dictionary: Record<string, T>): void {
  for (const [key, value] of Object.entries(dictionary)) {
    console.log(key, value);
  }
}

printDictionary({ apples: 3, oranges: 5 });

Step by Step Execution

Consider this code:

const stock: Record<string, number> = {
  apples: 8,
  bananas: 3
};

for (const key of Object.keys(stock)) {
  const quantity = stock[key];
  console.log(`${key} has ${quantity} items`);
}

Execution proceeds as follows:

  1. stock is created with two string keys: "apples" and "bananas".
  2. Object.keys(stock) produces the array ["apples", "bananas"].
  3. The first loop iteration assigns "apples" to key.
  4. stock[key] becomes stock["apples"], which is 8.
  5. The first message is printed: apples has 8 items.
  6. The next iteration assigns "bananas" to key.
  7. stock[key] becomes 3, and the second message is printed.

Bracket notation is required because key is a variable. stock.key would look for a literal property named "key".

Real World Use Cases

String-keyed dictionaries appear frequently in applications:

  • HTTP headers: Record<string, string> for header names and values.
  • Configuration settings: feature flags such as { darkMode: true, betaSearch: false }.
  • Inventory data: product IDs mapped to quantities.
  • Form validation: field names mapped to error messages.
  • Translation tables: language keys mapped to translated text.
  • API response metadata: dynamic names mapped to values.

Example: rendering validation errors in a UI:

const errors: Record<string, string> = {
  email: "Enter a valid email address",
  password: "Password must contain at least 12 characters"
};

for (const [field, message] of Object.entries(errors)) {
  console.log(`${field}: ${message}`);
}
Take Quiz

Real Codebase Usage

In production code, developers generally use Record<string, T> for simple dictionaries and choose a precise T value type.

Validate unknown data before using it

If data comes from JSON, an API, or user input, start with unknown and validate values before treating them as a specific type:

const settings: Record<string, unknown> = {
  retries: 3,
  debug: true
};

for (const [key, value] of Object.entries(settings)) {
  if (typeof value === "number") {
    console.log(`${key} is numeric: ${value}`);
  }
}

Use guard clauses while iterating

const labels: Record<string, string | undefined> = {
  save: "Save",
  cancel: undefined
};

for (const [key, label] of Object.entries(labels)) {
  if (label === undefined) {
    continue;
  }

  console.log(`Rendering ${key}: ${label}`);
}

Use Object.entries() for transformations

A common pattern is filtering a dictionary and rebuilding it:

const quantities: Record<string, number> = {
  apples: 4,
  bananas: 0,
  pears: 2
};

const available = Object.fromEntries(
  Object.entries(quantities).filter(([, quantity]) => quantity > 0)
);

console.log(available); // { apples: 4, pears: 2 }

Common Mistakes

Using for...of directly on an object

Objects are not iterable by default, so this does not work:

const dictionary: Record<string, number> = { apples: 2 };

// TypeError at runtime / TypeScript error
for (const key of dictionary) {
  console.log(key);
}

Use Object.keys(dictionary) instead.

Using dot notation with a variable key

const dictionary: Record<string, number> = { apples: 2 };
const key = "apples";

console.log(dictionary.key); // Looks for a property literally named "key"

Use bracket notation:

console.log(dictionary[key]); // 2

Using any unnecessarily

const data: { [index: string]: any } = {};

any lets you call invalid methods without a TypeScript error. Prefer a specific type:

const data: Record<string, number> = {};

If the values are truly unknown, use:

const data: Record<string, unknown> = {};

Forgetting inherited properties with for...in

for...in can include enumerable properties inherited from an object's prototype. For ordinary dictionaries, Object.keys() is usually the safer and clearer default. If you use for...in, check ownership:

for (const key in dictionary) {
  if (Object.prototype.hasOwnProperty.call(dictionary, key)) {
    console.log(key);
  }
}

Assuming a key always exists

A string index signature may imply a value exists, but real data can be incomplete. When missing values are possible, represent that in the type:

const labels: Record<string, string | undefined> = {};
const label = labels["save"];

if (label !== undefined) {
  console.log(label.toUpperCase());
}

Comparisons

ApproachProducesBest useNotes
Object.keys(obj)string[]You need keysLook up each value with obj[key].
Object.values(obj)Values arrayYou only need valuesKeys are not available in the loop.
Object.entries(obj)[key, value] pairsYou need bothUsually the most readable option for both.
for...inProperty namesLegacy or special object iterationCheck own properties when needed.
Map<string, T>Iterable key-value pairsFrequent additions, removals, or non-string keysA Map has methods such as .get(), .set(), and .has().

{ [key: string]: T } and Record<string, T> both describe string-keyed objects in many common cases:

const a: { [key: string]: number } = { apples: 2 };
const b: Record<string, number> = { apples: 2 };

Record<string, number> is often shorter and easier to read. An index signature can be more flexible when declared alongside named properties in an interface.

Take Quiz

Cheat Sheet

// A string-keyed dictionary
const counts: Record<string, number> = {
  apples: 2,
  pears: 5
};

// Keys
for (const key of Object.keys(counts)) {
  console.log(key);
}

// Values
for (const value of Object.values(counts)) {
  console.log(value);
}

// Keys and values
for (const [key, value] of Object.entries(counts)) {
  console.log(key, value);
}

// Dynamic key access: use brackets
const key = "apples";
console.log(counts[key]);
  • Prefer Record<string, T> for a dictionary with string keys.
  • Use the most specific possible T, such as string, number, or User.
  • Prefer unknown over any for unvalidated values.
  • Use Object.keys() for keys and Object.entries() for key-value pairs.
  • Do not use for...of directly on a plain object.
  • Use for...in carefully because it can visit inherited enumerable properties.

FAQ

How do I loop through the keys of an object in TypeScript?

Use Object.keys() with for...of:

for (const key of Object.keys(object)) {
  console.log(key);
}

How do I get both keys and values from a TypeScript dictionary?

Use Object.entries():

for (const [key, value] of Object.entries(dictionary)) {
  console.log(key, value);
}

Should I use { [key: string]: any } for a dictionary?

Usually no. Prefer Record<string, T> with a known value type. Use Record<string, unknown> when values are not yet known or validated.

Why does dictionary.key not work in a loop?

Dot notation searches for a literal property named key. For the value stored in the key variable, use dictionary[key].

Is for...in safe for iterating over object keys?

It can be, but it may include inherited enumerable properties. Object.keys() returns only the object's own enumerable keys and is usually preferred.

When should I use a Map instead of an object dictionary?

Use Map when you need built-in methods such as .has(), frequent updates, guaranteed iteration APIs, or keys that are not strings. Use an object for simple string-keyed data structures.

Does Object.keys() preserve TypeScript literal key types?

For a general object, TypeScript commonly types the result as string[]. This is appropriate for a dynamic string-indexed dictionary. For fixed object shapes, extra typing may be useful, but dynamic dictionaries should normally be handled as string-keyed data.

Take Quiz

Mini Project

Description

Build a small inventory report from a string-keyed dictionary. This reflects a common backend or scripting task: process dynamic product IDs and show only items that need attention.

Goal

Print an inventory report that lists each product and identifies products with low stock.

Requirements

Create a Record<string, number> containing at least four product quantities. Loop through every product key and quantity. Print each product name and its quantity. Print an additional low-stock warning for quantities below 5. Do not use any.

Take Quiz

Keep learning

Related questions

View more