Question

Key-Value Pairs in TypeScript: Objects, Records, and Maps

Question

Is it possible to use key-value pairs in TypeScript? If so, what is the recommended way to define, add, read, and type key-value data? Please provide practical examples.

Short Answer

TypeScript supports key-value data through plain objects, index signatures, the Record utility type, and JavaScript's Map. You will learn when to choose each option and how TypeScript helps ensure that keys and values have the expected types.

Concept

A key-value pair associates a unique identifier (the key) with a value.

For example, in this data:

const userRoles = {
  alice: "admin",
  bob: "editor"
};
  • alice is a key and "admin" is its value.
  • bob is a key and "editor" is its value.

TypeScript runs on JavaScript, so it uses JavaScript's built-in data structures. The most common choices are:

  • Object: best for simple string-keyed data and JSON-like data.
  • Record<K, V>: a TypeScript type for an object whose keys and values follow specific rules.
  • Map<K, V>: best when keys are dynamic, may not be strings, or when you need map-specific methods such as .set() and .has().

The important TypeScript addition is typing: it can verify that a dictionary contains valid values and, when possible, valid keys. This catches mistakes before the code runs.

Mental Model

Think of a key-value collection as a labelled storage cabinet:

  • A key is the label on a drawer, such as "theme".
  • A value is what is stored in that drawer, such as "dark".
  • An object is a simple cabinet where drawer labels are usually text.
  • A Map is a more flexible cabinet: labels can be text, numbers, or even objects.

TypeScript acts like an inventory rule. It can say, “Every drawer must contain a number,” or “Only these three drawer labels are allowed.”

Take Quiz

Syntax and Examples

A plain object is the simplest key-value structure:

const prices: { [key: string]: number } = {
  apple: 1.5,
  banana: 0.75
};

prices.orange = 1.25;

console.log(prices.apple);      // 1.5
console.log(prices["orange"]);  // 1.25

{ [key: string]: number } is an index signature. It means:

  • keys are strings
  • every value is a number

The same type can be written more clearly with Record:

const scores: Record<string, number> = {
  Maya: 98,
  Leo: 87
};

scores.Ana = 92;

If the allowed keys are known in advance, use a union of literal keys:

type ThemeName = "light" | "dark";

const themeLabels: Record<ThemeName, string> = {
  light: "Light mode",
  dark: "Dark mode"
};

console.log(themeLabels.dark); // "Dark mode"

For a Map, declare both the key and value types:

const productStock = new Map<string, number>();

productStock.set("keyboard", 12);
productStock.set("mouse", 30);

console.log(productStock.get("keyboard")); // 12
console.log(productStock.has("monitor"));  // false

Step by Step Execution

Consider this typed object:

const inventory: Record<string, number> = {
  notebook: 10
};

inventory.pen = 25;

const remaining = inventory.notebook - 2;

console.log(remaining);

Execution proceeds as follows:

  1. inventory is created with one key-value pair: "notebook" → 10.
  2. Record<string, number> tells TypeScript that any string key is allowed, but every stored value must be a number.
  3. inventory.pen = 25 adds "pen" → 25.
  4. inventory.notebook reads the value 10.
  5. 10 - 2 produces 8.
  6. The program prints 8.

This would be rejected by TypeScript because the value is not a number:

inventory.eraser = "sold out";

Real World Use Cases

Key-value data appears throughout applications:

  • Application settings: Record<string, boolean> for feature flags such as "newCheckout" → true.
  • API response lookup: store users by ID for fast access, such as usersById[userId].
  • Shopping cart quantities: product IDs mapped to quantities.
  • HTTP headers: header names mapped to header values.
  • Translations: language keys mapped to display text, such as "save" → "Save".
  • Caching: a Map can associate request identifiers or object instances with computed results.

For data that will be converted to JSON or sent through an API, plain objects are usually the natural choice. Map is not directly represented by JSON without conversion.

Take Quiz

Real Codebase Usage

In real projects, developers choose the structure based on the guarantees they need.

Validate unknown keys before using them

When a key comes from a user, URL, or API, it may not exist. Check it before relying on its value:

const statusMessages: Record<string, string> = {
  pending: "Waiting for approval",
  approved: "Approved"
};

function getStatusMessage(status: string): string {
  return statusMessages[status] ?? "Unknown status";
}

The ?? operator provides a fallback if the value is undefined or null.

Use fixed keys for configuration

type Environment = "development" | "production";

const apiBaseUrls: Record<Environment, string> = {
  development: "http://localhost:3000",
  production: "https://api.example.com"
};

This guarantees that both environments are configured and prevents misspellings such as "prodution".

Use Map for non-string keys

type Session = { userId: string };

const sessionLastSeen = new Map<Session, Date>();
const session: Session = { userId: "u1" };

sessionLastSeen.set(session, new Date());

A plain object converts ordinary object keys to strings, while a Map preserves the original object reference as a key.

Common Mistakes

Assuming TypeScript creates a special key-value type at runtime

Record is only a TypeScript type. It does not create a new JavaScript collection. At runtime, this is still a normal object:

const flags: Record<string, boolean> = {};

Use Map when you specifically need runtime map behavior such as .set() and .get().

Using an array as a dictionary

This works at runtime but is misleading and weakly models the intent:

const values: string[] = [];
values["name"] = "Ada"; // Incorrect approach

Use an object or Map instead:

const values: Record<string, string> = {};
values.name = "Ada";

Forgetting that Map.get() can return undefined

const quantities = new Map<string, number>();
quantities.set("book", 3);

const quantity = quantities.get("pen"); // number | undefined

Handle the missing case:

const quantity = quantities.get("pen") ?? 0;

Treating arbitrary input as a known key

With a fixed-key Record, a string from external input is not automatically a safe key. Validate it or provide a fallback before use.

Using dot notation for keys that are not valid identifiers

const headers: Record<string, string> = {
  "content-type": "application/json"
};

console.log(headers["content-type"]); // Correct

headers.content-type means subtraction, not access to the "content-type" key.

Comparisons

OptionBest forKey typesReading a valueNotes
Plain objectJSON-like and simple dataUsually stringsobj[key] or obj.nameMost common choice
Index signatureObject with dynamic keysstring or numberobj[key]Syntax: { [key: string]: Value }
Record<K, V>Clearly typed object dictionariesKeys described by Kobj[key]Readable alias for many dictionary types
Map<K, V>Dynamic data and non-string keysAny typemap.get(key)Has .set(), .has(), .delete(), and .size

Record<string, number> and { [key: string]: number } often describe the same kind of object. Choose Record when it makes the key and value relationship easier to read.

Use an object when you want simple data or JSON serialization. Use a Map when key identity matters, when keys are objects, or when map methods are useful.

Take Quiz

Cheat Sheet

// Dynamic string keys, numeric values
const counts: Record<string, number> = {};
counts.items = 3;

// Equivalent index signature
const counts2: { [key: string]: number } = {};

// Fixed set of required keys
type Role = "admin" | "member";
const permissions: Record<Role, boolean> = {
  admin: true,
  member: false
};

// Map with typed key and value
const cache = new Map<string, number>();
cache.set("answer", 42);
const value = cache.get("answer"); // number | undefined
  • Use Record<string, T> for a string-keyed object containing T values.
  • Use Record<"a" | "b", T> when allowed keys are known.
  • Use bracket notation for dynamic keys: object[key].
  • Use ?? to supply a default for a missing value.
  • Map.get() may return undefined.
  • Objects are easier to serialize as JSON; Map is more flexible at runtime.

FAQ

Does TypeScript have a dictionary type?

TypeScript does not have a separate built-in Dictionary keyword, but Record<string, T> and index signatures are standard ways to type dictionary-like objects.

Should I use Record or Map in TypeScript?

Use Record for plain, string-keyed object data. Use Map when you need non-string keys, .has(), .size, reliable insertion ordering APIs, or frequent additions and removals.

Can a TypeScript object have number keys?

Yes. You can use { [key: number]: string } or Record<number, string>. In ordinary JavaScript objects, property names are represented as strings at runtime, even when accessed with numbers.

How do I loop through key-value pairs in an object?

Use Object.entries():

const scores: Record<string, number> = { Ada: 10, Lin: 8 };

for (const [name, score] of Object.entries(scores)) {
  console.log(name, score);
}

How do I loop through a Map?

const scores = new Map<string, number>([["Ada", 10]]);

for (const [name, score] of scores) {
  console.log(name, score);
}

Can I store different value types in one key-value object?

Yes, with a union type, although a dedicated interface is often clearer when fields are known:

const data: Record<string, string | number | boolean> = {
  name: "Ada",
  age: 30,
  active: true
};

Why does TypeScript say a value may be undefined?

A dynamic key might not be present. This is especially explicit with Map.get(). Handle the missing value with a check or a default such as value ?? fallback.

Take Quiz

Mini Project

Description

Build a small shopping-cart quantity tracker. Product IDs are keys and the number of units selected is each value. This mirrors how applications often collect quantities before sending an order to an API.

Goal

Create typed functions that add products, read quantities safely, and calculate the total item count.

Requirements

Use a Record<string, number> to store product quantities.

Add at least two products to the cart.

Create a function that returns 0 for a product that is not in the cart.

Create a function that calculates the total number of items.

Do not allow non-numeric quantities in the cart.

Take Quiz

Keep learning

Related questions

View more