The quickest way to handle a TypeScript merge maps task is spreading both Maps into a new one. Each Map is an iterable of [key, value] pairs, so new Map([...a, ...b]) combines them, and the second Map wins on duplicate keys:
const merged = new Map([...austin, ...denver]); // Map<string, number>
Every snippet in this guide went through strict-mode tsc and then Node (TypeScript 7.0.2 and Node.js v22.22.2). The outputs are pasted straight from my terminal.
Merge two Maps in TypeScript with the spread operator
Here, a retailer tracks stock by SKU in Austin and Denver warehouses. The TypeScript spread operator works here because Maps are iterable:
const austin = new Map<string, number>([
["MUG-12", 40],
["BOOK-TS", 15],
["MAT-YOGA", 8],
]);
const denver = new Map<string, number>([
["BOOK-TS", 22],
["LAMP-LED", 30],
]);
const merged = new Map([...austin, ...denver]);
console.log(merged);
console.log("size:", merged.size);
console.log("austin untouched:", austin.get("BOOK-TS"), "| denver size:", denver.size);
Output:
Map(4) {
'MUG-12' => 40,
'BOOK-TS' => 22,
'MAT-YOGA' => 8,
'LAMP-LED' => 30
}
size: 4
austin untouched: 15 | denver size: 2

BOOK-TS takes Denver’s 22 but keeps its original second position.Neither source Map changes, and a repeated key keeps the slot where it first appeared. TypeScript infers Map<string, number> on its own.
Merge into an existing Map with for…of and set()
When you don’t want a copy, loop over the second Map and call set() to update the first in place. A for…of loop in TypeScript destructures each entry neatly:
const settings = new Map<string, string>([
["theme", "light"],
["currency", "USD"],
["timeZone", "America/New_York"],
]);
const userPrefs = new Map<string, string>([
["theme", "dark"],
["timeZone", "America/Chicago"],
]);
// Mutates settings in place - no new Map is created
for (const [key, value] of userPrefs) {
settings.set(key, value);
}
console.log(settings);
// forEach passes (value, key), not (key, value)
const copy = new Map(settings);
new Map([["region", "us-east-1"]]).forEach((value, key) => copy.set(key, value));
console.log(copy.get("region"), copy.size);
Output:
Map(3) {
'theme' => 'dark',
'currency' => 'USD',
'timeZone' => 'America/Chicago'
}
us-east-1 4
Watch the argument order with forEach(): the callback gets (value, key), the reverse of each entry. If you’re still building your starting Maps, see how to initialize a Map in TypeScript.
How do you handle duplicate keys when merging Maps?
Spread always lets the last value win. To add quantities, keep the first value or pick the larger one, pass a resolver:
// austin and denver Maps from the first example
function mergeWith<K, V>(a: Map<K, V>, b: Map<K, V>, resolve: (left: V, right: V, key: K) => V): Map<K, V> {
const result = new Map(a);
for (const [key, value] of b) {
const existing = result.get(key);
result.set(key, result.has(key) ? resolve(existing as V, value, key) : value);
}
return result;
}
console.log("sum: ", mergeWith(austin, denver, (x, y) => x + y));
console.log("keep first:", mergeWith(austin, denver, (x) => x));
console.log("max: ", mergeWith(austin, denver, (x, y) => Math.max(x, y)));
Output:
sum: Map(4) {
'MUG-12' => 40,
'BOOK-TS' => 37,
'MAT-YOGA' => 8,
'LAMP-LED' => 30
}
keep first: Map(4) {
'MUG-12' => 40,
'BOOK-TS' => 15,
'MAT-YOGA' => 8,
'LAMP-LED' => 30
}
max: Map(4) {
'MUG-12' => 40,
'BOOK-TS' => 22,
'MAT-YOGA' => 8,
'LAMP-LED' => 30
}

BOOK-TS becomes 37, 15 or 22.The resolver also gets the key, so you can treat some keys differently. If your data starts as an array of records, create a Map from an array first, then merge.
Merge any number of Maps with a typed generic function
A rest parameter lets one helper accept any number of Maps, and the generics carry the key and value types through:
// austin and denver Maps from the first example
const chicago = new Map<string, number>([["MUG-12", 12], ["PEN-BLK", 200]]);
function mergeMaps<K, V>(...maps: Map<K, V>[]): Map<K, V> {
const result = new Map<K, V>();
for (const map of maps) {
for (const [key, value] of map) result.set(key, value);
}
return result;
}
const all = mergeMaps(austin, denver, chicago); // Map<string, number>
console.log(all);
Output:
Map(5) {
'MUG-12' => 12,
'BOOK-TS' => 22,
'MAT-YOGA' => 8,
'LAMP-LED' => 30,
'PEN-BLK' => 200
}
I prefer this over long spread chains. For the arrow-function form, see generic arrow functions in TypeScript.
TypeScript errors when merged Map types don’t match
Merging a Map<string, number> with a Map<string, string> is usually a bug, and tsc catches it:
function mergeMaps<K, V>(...maps: Map<K, V>[]): Map<K, V> {
return new Map(maps.flatMap((m) => [...m]));
}
const prices = new Map<string, number>([["MUG-12", 14.99]]);
const labels = new Map<string, string>([["MUG-12", "Coffee mug"]]);
const typed = mergeMaps(prices, labels);
const spread = new Map([...prices, ...labels]);
const twoArgs = new Map(prices, labels);
Output:
merge_errors.ts(8,33): error TS2345: Argument of type 'Map<string, string>' is not assignable to parameter of type 'Map<string, number>'.
Type 'string' is not assignable to type 'number'.
merge_errors.ts(10,24): error TS2769: No overload matches this call.
The last overload gave the following error.
Argument of type '([string, string] | [string, number])[]' is not assignable to parameter of type 'readonly (readonly [string, string])[]'.
Type '[string, string] | [string, number]' is not assignable to type 'readonly [string, string]'.
Type '[string, number]' is not assignable to type 'readonly [string, string]'.
Type at position 1 in source is not compatible with type at position 1 in target.
Type 'number' is not assignable to type 'string'.
merge_errors.ts(12,33): error TS2554: Expected 0-1 arguments, but got 2.

I expected the spread to widen to string | number, but the constructor infers its types from the first entry and rejects the rest. And new Map(a, b) isn’t a merge; the constructor takes one iterable.
Merge Maps that hold objects without sharing references
Spread copies entries, not the objects inside them. Edit a value in the merged Map and the original changes too. structuredClone() copies Maps and their nested objects:
interface Store { city: string; salesUSD: number }
const q1 = new Map<string, Store>([["TX-01", { city: "Austin", salesUSD: 48250.5 }]]);
const q2 = new Map<string, Store>([["CO-02", { city: "Denver", salesUSD: 39120 }]]);
const shallow = new Map([...q1, ...q2]);
shallow.get("TX-01")!.salesUSD = 0;
console.log("q1 after editing shallow:", q1.get("TX-01"));
q1.get("TX-01")!.salesUSD = 48250.5;
const deep = new Map([...structuredClone(q1), ...structuredClone(q2)]);
deep.get("TX-01")!.salesUSD = 0;
console.log("q1 after editing deep: ", q1.get("TX-01"));
Output:
q1 after editing shallow: { city: 'Austin', salesUSD: 0 }
q1 after editing deep: { city: 'Austin', salesUSD: 48250.5 }
Clone only when you’ll mutate values. The deep clone guide compares other options, and merging objects in TypeScript helps when the resolver should combine two objects.
Merge a Map with a Record or plain object
Object.entries() turns a plain object into pairs you can spread next to a Map. Here, updated state sales tax rates override the old ones:
const salesTax = new Map<string, number>([["TX", 0.0625], ["CO", 0.029]]);
const updates: Record<string, number> = { CO: 0.03, IL: 0.0625 };
const rates = new Map([...salesTax, ...Object.entries(updates)]);
console.log(rates);
const asObject = Object.fromEntries(rates);
console.log(asObject);
console.log("IL tax on $120.00:", "$" + (120 * rates.get("IL")!).toFixed(2));
Output:
Map(3) { 'TX' => 0.0625, 'CO' => 0.03, 'IL' => 0.0625 }
{ TX: 0.0625, CO: 0.03, IL: 0.0625 }
IL tax on $120.00: $7.50

Object.fromEntries() goes the other way when an API expects an object. The trade-offs between the two shapes are in Record vs Map in TypeScript, and simple pairs are covered in TypeScript key-value pairs.
Common Map merge mistakes
Object spread doesn’t read Map entries; it copies own enumerable properties, and a Map has none. JSON.stringify() has the same blind spot:
// austin and denver Maps from the first example
const asObject = { ...austin, ...denver };
console.log("object spread:", asObject);
const correct = new Map([...austin, ...denver]);
console.log("JSON.stringify:", JSON.stringify(correct));
console.log("as entries: ", JSON.stringify([...correct]));
Output:
object spread: {}
JSON.stringify: {}
as entries: [["MUG-12",40],["BOOK-TS",22],["MAT-YOGA",8],["LAMP-LED",30]]
Both print empty objects without any error. Serialize [...map] instead, or follow converting a Map to an array in TypeScript.
Pick one of these next if you’re working with collections:
- Merge arrays of objects in TypeScript
- Convert a Set to an array in TypeScript
- Iterate over a Record in TypeScript
- Map an array of objects in TypeScript
Frequently asked questions
How do I merge two Maps in TypeScript?
Use new Map([...a, ...b]). It returns a new Map, leaves both sources unchanged, and values from b win on duplicate keys.
Which value wins when both Maps have the same key?
The one set last. With spread, that’s the later Map; the key keeps its original insertion position.
How do I sum values instead of overwriting them?
Use a resolver, as in the mergeWith helper above, or call set(key, (a.get(key) ?? 0) + value) in a loop.
Can I merge Maps with different value types?
Not without saying so. Both spread (TS2769) and a generic helper (TS2345) reject it; annotate new Map<string, string | number>(...) if you really want a union.
Does merging Maps copy nested objects?
No. It’s shallow. Wrap the sources in structuredClone() before merging if you plan to mutate the values.
Where is the Map constructor documented?
See the Map() constructor on MDN.

Bijay Kumar is a 13-time Microsoft MVP with more than 18 years in software development, and the founder of Python Guides and TSinfo Technologies. He started out building .NET and SharePoint solutions at HP, TCS and KPIT before moving into Python, machine learning and AI, and he also builds web apps with TypeScript and React. He writes the tutorials here himself, and every example is run before publishing so you see the real output. More about Bijay · Microsoft MVP profile · LinkedIn