A TypeScript Map is a collection of key-value pairs where every key appears only once and the entries keep the order in which we added them. It is the standard JavaScript Map class with type parameters added: Map<string, number> tells the compiler that every key is a string and every value is a number. We use a Map when we need quick lookups by key, keys that are not strings, or a collection that grows and shrinks while the program runs.
This guide shows how to create a Map, add, read, update and delete entries, and loop over them. It also covers the parts that cause bugs in real projects: get() returning undefined, turning a Map into JSON, sorting, grouping with Map.groupBy(), and choosing between a Map, a plain object and a Record. Every snippet was compiled with TypeScript 7.0 in strict mode and run on Node.js 22.
The Map collection is not the same thing as the array method map(). The array method transforms each element of an array and returns a new array. This article is about the collection.
Every common Map operation fits in one snippet. The sections after it explain each step in detail.
const ages = new Map<string, number>();
// 1. Add entries
ages.set("Lokesh", 37);
ages.set("Raj", 35);
ages.set("John", 40);
// 2. Get an entry
const age = ages.get("John"); // age = 40
const missing = ages.get("Brian") ?? 0; // missing = 0
// 3. Check a key
ages.has("Lokesh"); // true
ages.has("Brian"); // false
// 4. Size of the Map
const count = ages.size; // count = 3
// 5. Update an entry
ages.set("Raj", 36); // replaces 35
// 6. Iterate in insertion order
for (const [name, value] of ages) {
console.log(name, value); // Lokesh 37, Raj 36, John 40
}
// 7. Convert to JSON
const json = JSON.stringify(Object.fromEntries(ages));
// json = {"Lokesh":37,"Raj":36,"John":40}
// 8. Sort by value
const byAge = new Map([...ages].sort(([, a], [, b]) => a - b));
// byAge = Raj 36, Lokesh 37, John 40
// 9. Group a list (ES2024)
const groups = Map.groupBy([1, 2, 3, 4], (n) => (n % 2 === 0 ? "even" : "odd"));
// groups = odd => [1, 3], even => [2, 4]
// 10. Delete an entry
const isDeleted = ages.delete("Lokesh"); // isDeleted = true
// 11. Clear the whole Map
ages.clear(); // size = 0
1. Creating a Map in TypeScript
We create a Map with the new keyword. The two type parameters in angle brackets are the key type and the value type. In this guide, the Map stores people’s ages: the key is a name and the value is the age.
When we already know some entries, we pass them to the constructor as an array of [key, value] pairs. TypeScript reads those pairs and infers the types, so ages below becomes Map<string, number> without us writing the types. The set() method returns the Map itself, which lets us chain several calls.
// 1. Empty Map with explicit types
const empty = new Map<string, number>();
// 2. Initial entries; the type is inferred as Map<string, number>
const ages = new Map([
["Lokesh", 37],
["Raj", 35],
]);
// 3. Chained set() calls
const cities = new Map<string, string>()
.set("Lokesh", "Delhi")
.set("Raj", "Pune");
One detail matters for type safety. An empty new Map() with no type parameters and no entries becomes Map<any, any>, and the compiler stops checking what goes into it. For an empty Map, we always write the types: new Map<string, number>().
2. Adding, Reading, Updating and Deleting Entries
A Map has a short list of methods, and each one does exactly one job. size is a property, not a method, so we write ages.size without parentheses.
| Method or property | What it does | Returns |
|---|---|---|
| set(key, value) | Adds an entry, or replaces the value when the key already exists | The Map itself |
| get(key) | Reads the value stored for a key | The value, or undefined if the key is missing |
| has(key) | Checks whether a key exists | true or false |
| delete(key) | Removes one entry | true if the key existed, otherwise false |
| clear() | Removes all entries | undefined |
| size | Counts the entries | A number |
There is no separate “update” method. Calling set() with a key that already exists replaces the old value.
const ages = new Map<string, number>();
// Add and update
ages.set("Lokesh", 37);
ages.set("Raj", 35);
ages.set("Raj", 36); // replaces 35
// Read
const age = ages.get("Lokesh"); // age = 37
const unknown = ages.get("Brian"); // unknown = undefined
const found = ages.has("Raj"); // found = true
const count = ages.size; // count = 2
// Delete
const deleted = ages.delete("Raj"); // deleted = true
const again = ages.delete("Raj"); // again = false
ages.clear(); // size = 0
Counting things is one of the most common uses of a Map: words in a text, orders per customer, visits per page. We read the current count, use 0 when the key is new, and write the result back. The ?? operator (nullish coalescing) returns the right side only when the left side is null or undefined.
const visits = new Map<string, number>();
visits.set("home", (visits.get("home") ?? 0) + 1);
visits.set("home", (visits.get("home") ?? 0) + 1);
const homeVisits = visits.get("home"); // homeVisits = 2
3. Handling undefined From get()
The return type of get() is the value type plus undefined. For Map<string, number>, it is number | undefined, because the compiler cannot know whether a key exists at runtime. With strict mode turned on, using that value as a plain number is a compile error.
const ages = new Map<string, number>([["Lokesh", 37]]);
const age: number = ages.get("Lokesh");
if (ages.has("Lokesh")) {
const next = ages.get("Lokesh") + 1;
}
The TypeScript 7.0 compiler reports both lines:
src/err.ts(3,7): error TS2322: Type 'number | undefined' is not assignable to type 'number'.
Type 'undefined' is not assignable to type 'number'.
src/err.ts(6,16): error TS2532: Object is possibly 'undefined'.
The second error is common. TypeScript does not track which keys a Map contains, so a successful has() check does not change the type of a later get() call. There are three clean ways to handle the missing case.
// 1. Default value
const age1 = ages.get("Brian") ?? 0; // age1 = 0
// 2. Check the result; the type narrows to number
const age2 = ages.get("Lokesh");
if (age2 !== undefined) {
console.log(age2 + 1); // 38
}
// 3. Non-null assertion after has()
if (ages.has("Lokesh")) {
console.log(ages.get("Lokesh")! + 1); // 38
}
Each option fits a different case:
- Option 1 fits when a sensible default exists, such as 0 for a count.
- Option 2 is the one we prefer in most code: it does one lookup instead of two and needs no assertion.
- Option 3 works, but the ! operator turns off the undefined check for that expression, so the compiler will not warn us if a later change removes the key.
4. Iterating Over a Map
A Map always returns its entries in insertion order, the order in which the keys were first added. A for…of loop over the Map gives one [key, value] pair per step, and the brackets in const [name, age] split each pair into two variables (array destructuring). The keys() and values() methods return only one side, and the spread operator (…) turns them into arrays.
const ages = new Map([
["Lokesh", 37],
["Raj", 35],
["John", 40],
]);
// 1. Entries
for (const [name, age] of ages) {
console.log(name, age); // Lokesh 37, Raj 35, John 40
}
// 2. Keys and values
const names = [...ages.keys()]; // names = ["Lokesh", "Raj", "John"]
const values = [...ages.values()]; // values = [37, 35, 40]
// 3. forEach: value first, then key
ages.forEach((age, name) => console.log(name, age));
The forEach() callback receives the value first and the key second, which is the opposite of the for…of pair. When the key and the value have the same type, swapping them does not cause a compile error, so the parameter order needs attention.
Updating a key and re-adding a key affect the order differently. Calling set() on an existing key keeps the key in its original position, while deleting a key and adding it again moves it to the end.
ages.set("Lokesh", 38); // stays first
ages.delete("Raj");
ages.set("Raj", 35); // moves to the end
const order = [...ages.keys()]; // order = ["Lokesh", "John", "Raj"]
5. Converting a Map to an Array, Object or JSON
Many APIs expect plain objects or arrays, and JSON has no Map type. Two standard functions handle most conversions: Object.fromEntries() builds an object from [key, value] pairs, and Object.entries() turns an object back into pairs that the Map constructor accepts.
const ages = new Map([["Lokesh", 37], ["Raj", 35]]);
// 1. Map to array and object
const pairs = [...ages]; // pairs = [["Lokesh", 37], ["Raj", 35]]
const obj = Object.fromEntries(ages); // obj = { Lokesh: 37, Raj: 35 }
// 2. Object to Map
const fromObj = new Map(Object.entries({ John: 40 })); // John => 40
// 3. Map to JSON and back
const wrong = JSON.stringify(ages); // wrong = {}
const json = JSON.stringify(Object.fromEntries(ages)); // json = {"Lokesh":37,"Raj":35}
const parsed: Record<string, number> = JSON.parse(json);
const restored = new Map(Object.entries(parsed));
const rajAge = restored.get("Raj"); // rajAge = 35
The JSON.stringify(ages) line is a common production bug. JSON.stringify() writes only an object’s own enumerable properties, and a Map keeps its entries in internal storage, so the result is an empty object. No error is thrown, so the API response or the saved file contains no data and nothing reports the problem. We always convert the Map before calling JSON.stringify().
Object.fromEntries() is safe only for string or number keys. If the Map uses objects as keys, every key turns into the same string “[object Object]”. For such a Map, we serialize the array of pairs from […map] instead.
6. Sorting a Map and Grouping With Map.groupBy()
A Map has no sort() method, because its order is always the insertion order. To sort it, we copy the entries into an array with […ages], sort the array, and build a new Map from the sorted pairs. The original Map stays unchanged.
const ages = new Map([["Raj", 35], ["John", 40], ["Lokesh", 37]]);
// 1. By key
const byName = new Map([...ages].sort(([a], [b]) => a.localeCompare(b)));
// byName = John 40, Lokesh 37, Raj 35
// 2. By value
const byAge = new Map([...ages].sort(([, a], [, b]) => a - b));
// byAge = Raj 35, Lokesh 37, John 40
The parameters of the compare function use destructuring again. ([a], [b]) takes the first element of each pair, which is the key. ([, a], [, b]) skips the key with an empty slot before the comma and takes the second element, which is the value.
The static method Map.groupBy(), added in ES2024, takes a list and a function that returns a group key for each item. It returns a Map from each group key to the items in that group. Before ES2024, we wrote a loop with get() and set() for this. To use it in TypeScript, the lib (or target) setting in tsconfig.json must include ES2024 or later; otherwise the compiler reports that groupBy does not exist on MapConstructor.
type Status = "new" | "shipped" | "cancelled";
interface Order { id: number; status: Status; }
const orders: Order[] = [
{ id: 1, status: "new" },
{ id: 2, status: "shipped" },
{ id: 3, status: "new" },
];
const byStatus = Map.groupBy(orders, (o) => o.status); // Map<Status, Order[]>
const newIds = byStatus.get("new")?.map((o) => o.id); // newIds = [1, 3]
const statuses = [...byStatus.keys()]; // statuses = ["new", "shipped"]
TypeScript infers the result type Map<Status, Order[]> from the callback, so byStatus.get(“new”) is an array of orders or undefined. That is why the example uses the optional chaining operator ?. before calling map() on it. A status with no orders, such as “cancelled” here, does not appear in the result at all.
7. How a Map Compares Keys
A Map compares keys with the SameValueZero rule described in the ECMAScript specification. In practice, it works like the === operator, with one exception: NaN matches NaN. This rule leads to two behaviors that differ from plain objects.
// 1. The number 1 and the string "1" are different keys
const mixed = new Map<number | string, string>();
mixed.set(1, "number");
mixed.set("1", "string");
const size = mixed.size; // size = 2
// 2. A plain object turns both into the string "1"
const plain: Record<string, string> = {};
plain[1] = "number";
plain["1"] = "string";
const keys = Object.keys(plain).length; // keys = 1
// 3. Object keys are compared by reference
interface User { id: number; name: string; }
const lokesh: User = { id: 1, name: "Lokesh" };
const visits = new Map<User, number>([[lokesh, 3]]);
visits.get(lokesh); // 3
visits.get({ id: 1, name: "Lokesh" }); // undefined
A plain object converts both keys to the string “1”, so the second assignment overwrites the first. Object keys are compared by reference: a new object with the same fields is a different key, so get() returns undefined. When the “same” object can be re-created (for example, loaded again from a database), a stable ID such as lokesh.id is the safer key.
8. Map vs Object vs Record in TypeScript
TypeScript gives us three ways to store key-value data:
- a Map
- a plain object
- the Record<K, V> type, which describes an object whose keys are of type K and values of type V
A Record is still a plain object at runtime, so it behaves like an object in the comparison below. The MDN comparison of objects and maps is the source for the runtime rows.
| Need | Map | Object or Record |
|---|---|---|
| Key types | Any value: string, number, object, function | Strings and symbols; numbers become strings |
| Order of entries | Always insertion order | Integer-like keys come first in ascending order, then other keys in insertion order |
| Number of entries | map.size | Object.keys(obj).length |
| Frequent adds and deletes | Designed for it | Not optimized for it |
| JSON | Needs conversion first | Works with JSON.stringify() directly |
| Compiler checks a fixed set of keys | No | Yes, for example Record<“NEW” | “SHIPPED”, number> must have both keys |
| Built-in keys from the prototype | None | Inherits keys such as constructor and toString |
A short rule covers most cases. When the keys are known in advance and the data goes to or comes from JSON (configuration, API payloads), a Record or an interface is the better fit. When keys are added and removed at runtime, are not strings, or come from user input, a Map is the better fit.

The article TypeScript Record vs Map compares the two in more detail, and TypeScript Set covers the related collection that stores unique values without keys.
9. Running the Complete Example
The complete project on GitHub contains runnable versions of every snippet above, an index.ts that runs them in order, and tests for the helpers written with the built-in Node.js test runner. It uses TypeScript 7.0.2 and needs Node.js 22 or newer.
The tsconfig.json sets the ES2024 library for Map.groupBy(), uses Node.js-style ES modules, and turns on strict mode, which gives us the undefined checks from section 3.
{
"compilerOptions": {
"target": "es2024",
"lib": ["es2024"],
"module": "nodenext",
"strict": true,
"rootDir": ".",
"outDir": "dist",
"types": ["node"],
"skipLibCheck": true
},
"include": ["src", "test"]
}
After cloning the repository, we run these commands inside the typescript-maps folder:
npm install
npm start
npm test
The npm start script compiles the project and prints the values shown in the comments of each snippet. The npm test script compiles it again and runs five tests that cover counting, conversion to and from objects, sorting, grouping, and the empty {} result of JSON.stringify().
10. Conclusion
A TypeScript Map stores key-value pairs with typed keys and values, keeps insertion order, and gives us set(), get(), has(), delete() and clear() for everyday work. The parts that need extra care are get(), which can return undefined, and JSON.stringify(), which writes an empty object unless we convert the Map first. For data with a fixed set of string keys that is sent or stored as JSON, a Record is usually simpler. For data that changes at runtime, a Map is the better tool.
11. References
MDN and the ECMAScript specification describe how a Map behaves at runtime, and the TypeScript Handbook explains the generic type parameters used in every example.
- MDN: Map
- MDN: Map.groupBy()
- MDN: Object.fromEntries()
- MDN: Equality comparisons and sameness
- ECMAScript specification: Map objects
- TypeScript Handbook: Generics
- Node.js test runner
Happy Learning !!
What is the purpose of using the Map() iterator in javascript?
how to update map key
Keys are not supposed to be updated in maps.
I have a map that consists of key, value pairs where value is in the form of an array of objects i.e. basically of the form
myMap
a, 123
b , 45
c, 6789
I tried printing it on the console with the help of nested for loops but it just won’t run.
Could you help me out ??
How to import this Map in typescript file???
You don’t need to import anything. It’s part of the language.