ZetCode

TypeScript Maps

last modified October 15, 2026

A Map is a collection of key-value pairs. Unlike a plain object, a map accepts keys of any type, including numbers and objects, keeps the insertion order of its entries, and has a size property and methods for reading and changing entries. In TypeScript, the type Map<K, V> states the type of the keys K and of the values V.

Creating Maps

A map is created with the Map constructor, either empty or from an array of key-value pairs.

creating_maps.ts
const userRoles = new Map<number, string>();
userRoles.set(1, "Admin");
userRoles.set(2, "Editor");

const productPrices = new Map([
    ["Laptop", 999],
    ["Mouse", 29],
]);

console.log(userRoles);
console.log(productPrices);
console.log(userRoles.size, productPrices.size);

The empty map needs explicit type arguments: without <number, string>, TypeScript would not know what it will contain. For productPrices, the types are inferred from the initial pairs, so its type is Map<string, number>. The size property returns the number of entries.

$ npx tsx creating_maps.ts
Map(2) { 1 => 'Admin', 2 => 'Editor' }
Map(2) { 'Laptop' => 999, 'Mouse' => 29 }
2 2

Adding and Updating Entries

The set method adds a new entry or replaces the value of an existing key.

adding_entries.ts
const inventory = new Map<string, number>();

inventory.set("Apples", 50).set("Oranges", 30);
inventory.set("Apples", 45);

console.log(inventory);
// inventory.set("Pears", "many");  // Error: 'string' is not assignable to 'number'

The set method returns the map itself, so calls can be chained. Setting "Apples" again replaces its value; a key occurs in a map at most once. The commented line would not compile, because the values must be numbers.

$ npx tsx adding_entries.ts
Map(2) { 'Apples' => 45, 'Oranges' => 30 }

Accessing Values

The get method returns the value for a key. If the key is missing, it returns undefined.

accessing_values.ts
const capitals = new Map<string, string>([
    ["France", "Paris"],
    ["Japan", "Tokyo"],
]);

console.log(capitals.get("Japan"));
console.log(capitals.get("Germany"));

const capital = capitals.get("France");
console.log(capital?.toUpperCase());

console.log(capitals.get("Germany") ?? "unknown");

The return type of get is string | undefined. Because of this, TypeScript does not allow capital.toUpperCase() without a check; optional chaining or the ?? operator handle the missing case.

$ npx tsx accessing_values.ts
Tokyo
undefined
PARIS
unknown

Checking Key Existence

The has method tells whether a key is present.

checking_keys.ts
const colors = new Map<string, string>([
    ["red", "#ff0000"],
    ["empty", ""],
]);

console.log(colors.has("red"), colors.has("blue"));

for (const name of ["red", "empty", "blue"]) {
    const hex = colors.get(name);
    console.log(name, hex !== undefined ? `found '${hex}'` : "missing");
}

When reading a value, it is often simpler to call get and compare the result with undefined than to call has first. Note that the test must not be a plain truthiness check: the empty string stored under "empty" is a real value, but it is falsy.

A has check does not narrow the result of a later get call; the compiler still types it as string | undefined.

$ npx tsx checking_keys.ts
true false
red found '#ff0000'
empty found ''
blue missing

Removing Entries

The delete method removes one entry and clear removes all of them.

removing_entries.ts
const settings = new Map<string, boolean>([
    ["darkMode", true],
    ["notifications", false],
    ["autosave", true],
]);

console.log(settings.delete("notifications"));
console.log(settings.delete("missing"));
console.log(settings);

settings.clear();
console.log(settings.size);

The delete method returns true when the key existed and was removed, and false otherwise. Both methods change the map in place.

$ npx tsx removing_entries.ts
true
false
Map(2) { 'darkMode' => true, 'autosave' => true }
0

Iterating Maps

Maps are iterable. They yield their entries in insertion order.

iterating_maps.ts
const users = new Map<number, string>([
    [101, "Alice"],
    [102, "Bob"],
    [103, "Carol"],
]);

for (const [id, name] of users) {
    console.log(`${id}: ${name}`);
}

users.forEach((name, id) => {
    console.log(`${name} has id ${id}`);
});

console.log([...users.keys()], [...users.values()]);

A for...of loop over a map yields [key, value] pairs, which are destructured into id and name. The forEach callback receives the value first and the key second. The keys and values methods return iterators, which the spread operator turns into arrays.

$ npx tsx iterating_maps.ts
101: Alice
102: Bob
103: Carol
Alice has id 101
Bob has id 102
Carol has id 103
[ 101, 102, 103 ] [ 'Alice', 'Bob', 'Carol' ]

Converting Maps

Maps convert easily to arrays and to plain objects, and back.

map_to_array.ts
const scoreboard = new Map<string, number>([
    ["Team A", 3],
    ["Team B", 5],
    ["Team C", 1],
]);

const entries = [...scoreboard];
console.log(entries);

const ranking = [...scoreboard].sort((a, b) => b[1] - a[1]).map(([team]) => team);
console.log(ranking);

const asObject = Object.fromEntries(scoreboard);
console.log(asObject, JSON.stringify(asObject));

const backToMap = new Map(Object.entries(asObject));
console.log(backToMap.get("Team B"));

Spreading a map produces an array of pairs of type [string, number][], which can be sorted and transformed with array methods. Object.fromEntries builds an object from a map, which is needed for JSON.stringify: a map itself is serialized as an empty object. Object.entries and the Map constructor do the reverse.

$ npx tsx map_to_array.ts
[ [ 'Team A', 3 ], [ 'Team B', 5 ], [ 'Team C', 1 ] ]
[ 'Team B', 'Team A', 'Team C' ]
{ 'Team A': 3, 'Team B': 5, 'Team C': 1 } {"Team A":3,"Team B":5,"Team C":1}
5

Grouping and Counting

Maps are well suited for counting and grouping values.

grouping.ts
const words = ["apple", "avocado", "banana", "blueberry", "cherry", "apricot"];

const counts = new Map<string, number>();
for (const word of words) {
    const letter = word[0];
    counts.set(letter, (counts.get(letter) ?? 0) + 1);
}
console.log(counts);

const groups = new Map<number, string[]>();
for (const word of words) {
    const list = groups.get(word.length) ?? [];
    list.push(word);
    groups.set(word.length, list);
}
console.log(groups);

For counting, the current count is read with get, a missing key defaults to 0 with ??, and the incremented value is stored back. Grouping works the same way with arrays as values; here the words are grouped by their length, a number key. Newer runtimes also offer Map.groupBy for the second task.

$ npx tsx grouping.ts
Map(3) { 'a' => 3, 'b' => 2, 'c' => 1 }
Map(4) {
  5 => [ 'apple' ],
  7 => [ 'avocado', 'apricot' ],
  6 => [ 'banana', 'cherry' ],
  9 => [ 'blueberry' ]
}

Objects as Keys

Map keys can be objects. They are compared by reference, not by content.

object_keys.ts
type User = { name: string };

const alice: User = { name: "Alice" };
const bob: User = { name: "Bob" };

const lastLogin = new Map<User, string>();
lastLogin.set(alice, "2026-10-14");
lastLogin.set(bob, "2026-10-15");

console.log(lastLogin.get(alice));
console.log(lastLogin.get({ name: "Alice" }));

The map associates data with objects without adding properties to them. The second lookup returns undefined: the object literal has the same content as alice, but it is a different object. A plain object cannot do this at all, because its keys are always strings or symbols. For caches tied to objects, a WeakMap lets the keys be garbage-collected.

$ npx tsx object_keys.ts
2026-10-14
undefined

Map versus Record

For a fixed or string-keyed set of entries, a plain object typed with Record is often enough.

map_vs_record.ts
type Currency = "EUR" | "USD" | "GBP";

const symbols: Record<Currency, string> = {
    EUR: "€",
    USD: "$",
    GBP: "£",
};

const rates = new Map<Currency, number>();
rates.set("USD", 1.08);

console.log(symbols.EUR, Object.keys(symbols).length);
console.log(rates.get("USD"), rates.get("GBP"), rates.size);

The Record<Currency, string> type requires a value for every currency, and symbols.EUR has the type string. A map can hold any subset of keys, so rates.get always returns number | undefined. Use an object when the keys are known in advance and the data is mostly read; use a map when entries are added and removed at runtime, when keys are not strings, or when the order and size matter.

$ npx tsx map_vs_record.ts
€ 3
1.08 undefined 1

Best Practices

Source

MDN: Map

This tutorial covered maps in TypeScript. With type parameters, the compiler checks every key and value that goes into a map and knows the type of everything that comes out of it.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all TypeScript tutorials.