In TypeScript a Map is the ordinary JavaScript Map with types for its keys and values: new Map<string, number>(). Keys can be any value, entries come back in the order they were first added, size counts them, and get returns V | undefined. JSON.stringify does not serialize a Map, so convert it first.
| Call | Does | Type you get (Map<string, number>) |
|---|---|---|
| map.set(k, v) | Adds or updates an entry | Map<string, number> (chainable) |
| map.get(k) | Reads a value | number | undefined |
| map.has(k) | Checks a key | boolean |
| map.delete(k) | Removes an entry | boolean (true if it existed) |
| map.size | Counts entries | number |
| for (const [k, v] of map) | Loops in insertion order | k: string, v: number |
Updated
The playground below is a real Map running in your browser. Add, update and delete entries and watch three things: the order entries keep, what size reports, and the difference between stringifying the Map itself and stringifying a converted copy.
Entries in iteration order (size: 3)
JSON.stringify(map) // {}
JSON.stringify(Object.fromEntries(map)) // {"tea":3,"cake":5,"juice":4}The ?? fallback handles the first sighting of a word, which is why get returning number | undefined is helpful rather than annoying.
function countWords(text: string): Map<string, number> {
const counts = new Map<string, number>();
for (const word of text.toLowerCase().split(/\s+/).filter(Boolean)) {
counts.set(word, (counts.get(word) ?? 0) + 1);
}
return counts;
}
countWords("to be or not to be"); // Map { "to" => 2, "be" => 2, "or" => 1, "not" => 1 }An array of pairs keeps every entry and its order. Object.fromEntries is the readable choice when every key is a string.
const stock = new Map<string, number>([["tea", 3], ["cake", 5]]);
const asPairs = JSON.stringify([...stock]); // '[["tea",3],["cake",5]]'
const asObject = JSON.stringify(Object.fromEntries(stock)); // '{"tea":3,"cake":5}'
const restored = new Map<string, number>(JSON.parse(asPairs));
const fromObject = new Map<string, number>(Object.entries(JSON.parse(asObject)));Inside total, calling prices.set would be a compile error, so callers can pass their Map without a defensive copy.
function total(prices: ReadonlyMap<string, number>, cart: string[]): number {
let sum = 0;
for (const item of cart) sum += prices.get(item) ?? 0;
return sum;
}
A plain object inherits from Object.prototype, so names such as constructor are already there, and its keys are always strings or symbols. A Map starts empty, accepts any value as a key and reports its entry count through size.
Record is a type, not a container. It describes a plain object, so everything above about objects applies to it at runtime, including native JSON support that a Map lacks.
Map compares keys with the SameValueZero rule. The number 1 and the string "1" are different keys, NaN matches NaN, and an object only matches itself. A plain object would turn 1 into "1" and merge the two.
That makes a Map the natural cache for values keyed by a DOM element, a function or a request object, cases where an object would need an invented string id.
Accept ReadonlyMap<K, V> in functions that only read. It removes set, delete and clear from the type, so the signature itself tells callers their Map will not be changed.
When you build a Map from an array of pairs, TypeScript infers the types from the pairs. Add an explicit generic when the array could be empty or when the pairs mix types you want kept apart.
Pass the key and value types as generic arguments: const prices = new Map<string, number>(). For a field or parameter, write the type as Map<string, number>, or ReadonlyMap<string, number> when the function should only read it.
Because the compiler cannot know which keys are in the Map at a given moment. get is typed as V | undefined, so you narrow with an if check, a ?? fallback or a has() test before treating the value as V.
A Map keeps its entries in an internal slot, not as object properties, so JSON sees an empty object. Convert first: Object.fromEntries(map) for string keys, or [...map] for an array of pairs that survives any key type.
Use a Record for a fixed set of named keys and for data that goes to and from JSON. Use a Map when entries come and go while the app runs, when keys are numbers or objects, or when insertion order matters.
No. Setting an existing key changes its value but keeps its original position. To move it to the end, delete the key and set it again.
Related: TypeScript Record type, Loops in TypeScript, TypeScript data types, TypeScript vs JavaScript, JavaScript loops compared.
Start for free or see all free tools. We.Inc is free to start, with no card needed. AI building is on paid plans from $20 a month.
We.Inc is an AI-powered website builder you can resell under your own brand. Launch a branded client dashboard, bill on Stripe Connect, and deliver AI-generated websites in minutes. White-label plans start at $99 a month for 25 client sites, with a 7-day free trial and no per-site fees.