Is there a simple way to merge ES6 Maps together (like Object.assign
)? And while we're at it, what about ES6 Sets (like Array.concat
)?

- 4,298
- 4
- 21
- 19
-
4This blog post has some insight. http://www.2ality.com/2015/01/es6-set-operations.html – lemieuxster Aug 14 '15 at 01:16
-
AFAIK for the _Map_ you'll need to use `for..of` because `key` can be any type – Paul S. Aug 14 '15 at 01:17
-
1I tried a couple of the higher upvoted suggestions here, without success. The "union" function provided at https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set#implementing_basic_set_operations worked well. As a one-liner, here (note: as one-liner, I needed to add a semicolon before the "for" statement): `function union(setA, setB) { let _union = new Set(setA); for (let elem of setB) { _union.add(elem) } return _union }; set1 = new Set(['apple']); set2 = new Set(['banana']); set3 = union(set1,set2); console.log(set3) // Set [ "apple", "banana" ]` – Victoria Stuart Feb 26 '21 at 03:52
15 Answers
For sets:
var merged = new Set([...set1, ...set2, ...set3])
For maps:
var merged = new Map([...map1, ...map2, ...map3])
Note that if multiple maps have the same key, the value of the merged map will be the value of the last merging map with that key.

- 274,082
- 63
- 437
- 513
-
6Documentation on [`Map`](https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Map): “Constructor: `new Map([iterable])`”, “`iterable` is an Array or other iterable object whose elements are key-value pairs (2-element Arrays). Each key-value pair is added to the new Map.” — just as a reference. – Sebastian Simon Aug 14 '15 at 01:28
-
60For large Sets, just be warned that this iterates the content of both Sets twice, once to create a temporary array containing the union of the two sets, then passes that temporary array to the Set constructor where it is iterated again to create the new Set. – jfriend00 Aug 14 '15 at 03:10
-
2
-
1
-
2@torazaburo: as jfriend00 said, Oriols solution does create unnecessary intermediate arrays. Passing an iterator to the `Map` constructor avoids their memory consumption. – Bergi Aug 14 '15 at 07:33
-
3
-
@peterh you should try first before speaking. Example - `var a = new Set([1,2,3])`, `var b = new Set([3,4,5])`, `console.log(new Set([...a, ...b]))` prints - `Set { 1, 2, 3, 4, 5 }`. – Jyotman Singh Mar 31 '17 at 08:16
-
2@JyotmanSingh You are right, I am sorry. I didn't notice the `...` :-( It is a pretty good answer! – peterh Mar 31 '17 at 08:46
-
This answer is exactly correct. Just a pointer for distracted users: this doesn't work in ES5 (or TypeScript targeted to ES5). – Guillermo Prandi Feb 04 '18 at 20:52
-
-
1I did not want a new Set. I wanted the original Set with the new items added in a single simple preferably built in function. The ES6 Set is woefully inadequate. No extend/update, no intersection, no union as built in operations. Fails Data Structures 101. – Samantha Atkins Feb 02 '19 at 00:44
-
1Doesn't work in TypeScript. Requires each `...map` to be converted to `...Array.from(map)` – devuxer Dec 06 '19 at 22:37
-
3@devuxer you need to enable `compilerOptions.downlevelIteration` in your tsconfig.json to get rid of the compiler error. See https://stackoverflow.com/questions/53441292/why-downleveliteration-is-not-on-by-default – Max Apr 18 '20 at 07:44
-
It would be so awesome if a callback that handles duplicate keys could be passed to this constructor, e.g.: const mergedMap = new Map([...map1, ...map2], (value1, value2) => customMergeValues(value1, value2)) This way, you can choose a custom way of handling duplicate keys instead of just setting the value to the one of the last duplicate key in the entry list. Update: YES -> https://github.com/tc39/proposal-collection-methods – marvatron Feb 19 '22 at 22:50
For reasons I do not understand, you cannot directly add the contents of one Set to another with a built-in method. Operations like union, intersect, merge, etc... are pretty basic set operations, but are not built-in. Fortunately, you can construct these all yourself fairly easily.
[Added in 2021] - There is now a proposal to add new Set/Map methods for these types of operations, but the timing of implementation is not immediately clear. They appear to be in Stage 2 of the spec process.
To implement a merge operation (merging the contents of one Set into another or one Map into another), you can do this with a single .forEach()
line:
var s = new Set([1,2,3]);
var t = new Set([4,5,6]);
t.forEach(s.add, s);
console.log(s); // 1,2,3,4,5,6
And, for a Map
, you could do this:
var s = new Map([["key1", 1], ["key2", 2]]);
var t = new Map([["key3", 3], ["key4", 4]]);
t.forEach(function(value, key) {
s.set(key, value);
});
Or, in ES6 syntax:
t.forEach((value, key) => s.set(key, value));
[Added in 2021]
Since there is now an official proposal for new Set methods, you could use this polyfill for the proposed .union()
method that would work in ES6+ versions of ECMAScript. Note, per the spec, this returns a new Set that is the union of two other sets. It does not merge the contents of one set into another and this implements the type checking that is specified in the proposal.
if (!Set.prototype.union) {
Set.prototype.union = function(iterable) {
if (typeof this !== "object") {
throw new TypeError("Must be of object type");
}
const Species = this.constructor[Symbol.species];
const newSet = new Species(this);
if (typeof newSet.add !== "function") {
throw new TypeError("add method on new set species is not callable");
}
for (item of iterable) {
newSet.add(item);
}
return newSet;
}
}
Or, here's a more complete version that models the ECMAScript process for getting the species constructor more completely and has been adapted to run on older versions of Javascript that might not even have Symbol
or have Symbol.species
set:
if (!Set.prototype.union) {
Set.prototype.union = function(iterable) {
if (typeof this !== "object") {
throw new TypeError("Must be of object type");
}
const Species = getSpeciesConstructor(this, Set);
const newSet = new Species(this);
if (typeof newSet.add !== "function") {
throw new TypeError("add method on new set species is not callable");
}
for (item of iterable) {
newSet.add(item);
}
return newSet;
}
}
function isConstructor(C) {
return typeof C === "function" && typeof C.prototype === "object";
}
function getSpeciesConstructor(obj, defaultConstructor) {
const C = obj.constructor;
if (!C) return defaultConstructor;
if (typeof C !== "function") {
throw new TypeError("constructor is not a function");
}
// use try/catch here to handle backward compatibility when Symbol does not exist
let S;
try {
S = C[Symbol.species];
if (!S) {
// no S, so use C
S = C;
}
} catch (e) {
// No Symbol so use C
S = C;
}
if (!isConstructor(S)) {
throw new TypeError("constructor function is not a constructor");
}
return S;
}
FYI, if you want a simple subclass of the built-in Set
object that contains a .merge()
method, you can use this:
// subclass of Set that adds new methods
// Except where otherwise noted, arguments to methods
// can be a Set, anything derived from it or an Array
// Any method that returns a new Set returns whatever class the this object is
// allowing SetEx to be subclassed and these methods will return that subclass
// For this to work properly, subclasses must not change behavior of SetEx methods
//
// Note that if the contructor for SetEx is passed one or more iterables,
// it will iterate them and add the individual elements of those iterables to the Set
// If you want a Set itself added to the Set, then use the .add() method
// which remains unchanged from the original Set object. This way you have
// a choice about how you want to add things and can do it either way.
class SetEx extends Set {
// create a new SetEx populated with the contents of one or more iterables
constructor(...iterables) {
super();
this.merge(...iterables);
}
// merge the items from one or more iterables into this set
merge(...iterables) {
for (let iterable of iterables) {
for (let item of iterable) {
this.add(item);
}
}
return this;
}
// return new SetEx object that is union of all sets passed in with the current set
union(...sets) {
let newSet = new this.constructor(...sets);
newSet.merge(this);
return newSet;
}
// return a new SetEx that contains the items that are in both sets
intersect(target) {
let newSet = new this.constructor();
for (let item of this) {
if (target.has(item)) {
newSet.add(item);
}
}
return newSet;
}
// return a new SetEx that contains the items that are in this set, but not in target
// target must be a Set (or something that supports .has(item) such as a Map)
diff(target) {
let newSet = new this.constructor();
for (let item of this) {
if (!target.has(item)) {
newSet.add(item);
}
}
return newSet;
}
// target can be either a Set or an Array
// return boolean which indicates if target set contains exactly same elements as this
// target elements are iterated and checked for this.has(item)
sameItems(target) {
let tsize;
if ("size" in target) {
tsize = target.size;
} else if ("length" in target) {
tsize = target.length;
} else {
throw new TypeError("target must be an iterable like a Set with .size or .length");
}
if (tsize !== this.size) {
return false;
}
for (let item of target) {
if (!this.has(item)) {
return false;
}
}
return true;
}
}
module.exports = SetEx;
This is meant to be in it's own file setex.js that you can then require()
into node.js and use in place of the built-in Set.

- 683,504
- 96
- 985
- 979
-
4I don't think `new Set(s, t)`. works. The `t` parameter is ignored. Also, it is obviously not reasonable behavior to have `add` detect the type of its parameter and if a set add the elements of the set, because then there would be no way to add a set itself to a set. – Aug 14 '15 at 02:31
-
@torazaburo - as for the `.add()` method taking a Set, I understand your point. I just find that of far less use than being able to combine sets using `.add()` as I've never ever had a need for a Set or Sets, but I've had a need to merge sets many times. Just a matter of opinion of usefulness of one behavior vs. the other. – jfriend00 Aug 14 '15 at 02:38
-
Argh, I hate that this doesn't work for maps: `n.forEach(m.add, m)` - it does invert key/value pairs! – Bergi Aug 14 '15 at 05:09
-
@Bergi - yeah, it is odd that `Map.prototype.forEach()` and `Map.prototype.set()` have reversed arguments. Seems like an oversight by someone. It forces more code when trying to use them together. – jfriend00 Aug 15 '15 at 00:23
-
1@jfriend00: OTOH, it kinda makes sense. `set` parameter order is natural for key/value pairs, `forEach` is aligned with `Array`s `forEach` method (and things like `$.each` or `_.each` that also enumerate objects). – Bergi Aug 16 '15 at 18:03
-
Add should not do magic if you pass a set, because you might want to nest a set inside a set. But it would make sense for add to take multiple parameters and add them all, then you could do `mySet.add(1,2,3,4)` or even `mySet.add(...anotherSet)`. – rjmunro May 08 '19 at 14:35
-
Added standalone polyfill for [proposed](https://tc39.es/proposal-set-methods/) `.union()` method. – jfriend00 Oct 01 '21 at 21:13
-
-
@Olivier - This [link](https://github.com/tc39/proposal-set-methods) says it's now at Stage 3 and it's also listed [here](https://github.com/tc39/proposals) as Stage 3. Perhaps it is finally progressing. – jfriend00 Mar 14 '23 at 22:45
-
@jfriend00 it seems! I was looking at [this](https://tc39.es/proposal-set-methods/) link as well as [this](https://www.proposals.es/proposals/New%20Set%20methods) one. I assumed the first one was official... either way, the github is definately more reliable. – Olivier Mar 16 '23 at 01:23
Here's my solution using generators:
For Maps:
let map1 = new Map(), map2 = new Map();
map1.set('a', 'foo');
map1.set('b', 'bar');
map2.set('b', 'baz');
map2.set('c', 'bazz');
let map3 = new Map(function*() { yield* map1; yield* map2; }());
console.log(Array.from(map3)); // Result: [ [ 'a', 'foo' ], [ 'b', 'baz' ], [ 'c', 'bazz' ] ]
For Sets:
let set1 = new Set(['foo', 'bar']), set2 = new Set(['bar', 'baz']);
let set3 = new Set(function*() { yield* set1; yield* set2; }());
console.log(Array.from(set3)); // Result: [ 'foo', 'bar', 'baz' ]
-
48
-
4nice also `m2.forEach((k,v)=>m1.set(k,v))` if you want easy browser support – caub Jul 22 '17 at 11:02
-
11@caub nice solution but remeber that the first parameter of forEach is value so your function should be m2.forEach((v,k)=>m1.set(k,v)); – David Noreña Jan 20 '18 at 22:16
Edit:
I benchmarked my original solution against other solutions suggests here and found that it is very inefficient.
The benchmark itself is very interesting (link) It compares 3 solutions (higher is better):
- @fregante (formerly called @bfred.it) solution, which adds values one by one (14,955 op/sec)
- @jameslk's solution, which uses a self invoking generator (5,089 op/sec)
- my own, which uses reduce & spread (3,434 op/sec)
As you can see, @fregante's solution is definitely the winner.
Performance + Immutability
With that in mind, here's a slightly modified version which doesn't mutates the original set and excepts a variable number of iterables to combine as arguments:
function union(...iterables) { const set = new Set(); for (const iterable of iterables) { for (const item of iterable) { set.add(item); } } return set; }
Usage:
const a = new Set([1, 2, 3]); const b = new Set([1, 3, 5]); const c = new Set([4, 5, 6]); union(a,b,c) // {1, 2, 3, 4, 5, 6}
Original Answer
I would like to suggest another approach, using reduce
and the spread
operator:
Implementation
function union (sets) {
return sets.reduce((combined, list) => {
return new Set([...combined, ...list]);
}, new Set());
}
Usage:
const a = new Set([1, 2, 3]);
const b = new Set([1, 3, 5]);
const c = new Set([4, 5, 6]);
union([a, b, c]) // {1, 2, 3, 4, 5, 6}
Tip:
We can also make use of the rest
operator to make the interface a bit nicer:
function union (...sets) {
return sets.reduce((combined, list) => {
return new Set([...combined, ...list]);
}, new Set());
}
Now, instead of passing an array of sets, we can pass an arbitrary number of arguments of sets:
union(a, b, c) // {1, 2, 3, 4, 5, 6}
-
-
1Hi @Bergi, you are right. Thanks for raising my awareness (: I've tested my solutions against others suggested here and proved it for myself. Also, I've edited my answer to reflect that. Please consider removing your downvote. – Asaf Katz May 11 '18 at 18:40
-
1Great, thanks for the performance comparison. Funny how the "unelegant" solution is fastest ;) Came here to look for an improvement over `forof` and `add`, which seems just very inefficient. I really wish for an `addAll(iterable)` method on Sets – Bruno Schäpper Nov 09 '18 at 08:14
-
1Typescript version: `function union
(...iterables: Array – ndp Jan 30 '19 at 00:36>): Set { const set = new Set (); iterables.forEach(iterable => { iterable.forEach(item => set.add(item)) }) return set } ` -
6The jsperf link near the top of your answer seems busted. Also, you refer to bfred's solution which I don't see anywhere here. – jfriend00 Aug 24 '19 at 16:36
-
Minor improvement: initialize the Set directly with the first iterable: `function union(iterable, ...iterables) { const set = new Set(iterable); ...` then the rest of the loop as before. Probably won't make *much* difference, but maybe the engine can optimize better compared to adding one by one. – nickf Nov 14 '19 at 23:19
-
2@jfriend00 when I visited `bfred.it` I got a twitter account of `fregante` so maybe it is fregante's answer! – Al-Mothafar Jun 01 '20 at 15:54
-
@AsafKatz jsperf has been shut down. Have you got the performance test code somewhere else easily at hand and shareable? – Magne Dec 04 '20 at 10:31
-
The approved answer is great but that creates a new set every time.
If you want to mutate an existing object instead, use a helper function
In the near future, you will be able to just use setA.union(setB)
Set
function concatSets(set, ...iterables) {
for (const iterable of iterables) {
for (const item of iterable) {
set.add(item);
}
}
}
Usage:
const setA = new Set([1, 2, 3]);
const setB = new Set([4, 5, 6]);
const setC = new Set([7, 8, 9]);
concatSets(setA, setB, setC);
// setA will have items 1, 2, 3, 4, 5, 6, 7, 8, 9
Map
function concatMaps(map, ...iterables) {
for (const iterable of iterables) {
for (const item of iterable) {
map.set(...item);
}
}
}
Usage:
const mapA = new Map().set('S', 1).set('P', 2);
const mapB = new Map().set('Q', 3).set('R', 4);
concatMaps(mapA, mapB);
// mapA will have items ['S', 1], ['P', 2], ['Q', 3], ['R', 4]

- 29,050
- 14
- 119
- 159
To merge the sets in the array Sets, you can do
var Sets = [set1, set2, set3];
var merged = new Set([].concat(...Sets.map(set => Array.from(set))));
It is slightly mysterious to me why the following, which should be equivalent, fails at least in Babel:
var merged = new Set([].concat(...Sets.map(Array.from)));
-
`Array.from` takes additional parameters, the second being a mapping function. `Array.prototype.map` passes three arguments to its callback: `(value, index, array)`, so it's effectively calling `Sets.map((set, index, array) => Array.from(set, index, array)`. Obviously, `index` is a number and not a mapping function so it fails. – nickf Nov 14 '19 at 23:23
Based off of Asaf Katz's answer, here's a typescript version:
export function union<T> (...iterables: Array<Set<T>>): Set<T> {
const set = new Set<T>()
iterables.forEach(iterable => {
iterable.forEach(item => set.add(item))
})
return set
}

- 21,546
- 5
- 36
- 52
It does not make any sense to call new Set(...anArrayOrSet)
when adding multiple elements (from either an array or another set) to an existing set.
I use this in a reduce
function, and it is just plain silly. Even if you have the ...array
spread operator available, you should not use it in this case, as it wastes processor, memory, and time resources.
// Add any Map or Set to another
function addAll(target, source) {
if (target instanceof Map) {
Array.from(source.entries()).forEach(it => target.set(it[0], it[1]))
} else if (target instanceof Set) {
source.forEach(it => target.add(it))
}
}
Demo Snippet
// Add any Map or Set to another
function addAll(target, source) {
if (target instanceof Map) {
Array.from(source.entries()).forEach(it => target.set(it[0], it[1]))
} else if (target instanceof Set) {
source.forEach(it => target.add(it))
}
}
const items1 = ['a', 'b', 'c']
const items2 = ['a', 'b', 'c', 'd']
const items3 = ['d', 'e']
let set
set = new Set(items1)
addAll(set, items2)
addAll(set, items3)
console.log('adding array to set', Array.from(set))
set = new Set(items1)
addAll(set, new Set(items2))
addAll(set, new Set(items3))
console.log('adding set to set', Array.from(set))
const map1 = [
['a', 1],
['b', 2],
['c', 3]
]
const map2 = [
['a', 1],
['b', 2],
['c', 3],
['d', 4]
]
const map3 = [
['d', 4],
['e', 5]
]
const map = new Map(map1)
addAll(map, new Map(map2))
addAll(map, new Map(map3))
console.log('adding map to map',
'keys', Array.from(map.keys()),
'values', Array.from(map.values()))

- 27,002
- 5
- 88
- 78
I created a helper method to merge maps and handle the values of duplicate keys in any pair-wise way desired:
const mergeMaps = (map1, map2, combineValuesOfDuplicateKeys) => {
const mapCopy1 = new Map(map1);
const mapCopy2 = new Map(map2);
mapCopy1.forEach((value, key) => {
if (!mapCopy2.has(key)) {
mapCopy2.set(key, value);
} else {
const newValue = combineValuesOfDuplicateKeys
? combineValuesOfDuplicateKeys(value, mapCopy2.get(key))
: mapCopy2.get(key);
mapCopy2.set(key, newValue);
mapCopy1.delete(key);
}
});
return new Map([...mapCopy1, ...mapCopy2]);
};
const mergeMaps = (map1, map2, combineValuesOfDuplicateKeys) => {
const mapCopy1 = new Map(map1);
const mapCopy2 = new Map(map2);
mapCopy1.forEach((value, key) => {
if (!mapCopy2.has(key)) {
mapCopy2.set(key, value);
} else {
const newValue = combineValuesOfDuplicateKeys
? combineValuesOfDuplicateKeys(value, mapCopy2.get(key))
: mapCopy2.get(key);
mapCopy2.set(key, newValue);
mapCopy1.delete(key);
}
});
return new Map([...mapCopy1, ...mapCopy2]);
};
const map1 = new Map([
["key1", 1],
["key2", 2]
]);
const map2 = new Map([
["key2", 3],
["key4", 4]
]);
const show = (object) => {
return JSON.stringify(Array.from(object), null, 2)
}
document.getElementById("app").innerHTML = `
<h1>Maps are awesome!</h1>
<div>map1 = ${show(map1)}</div>
<div>map2 = ${show(map2)}</div><br>
<div>Set value of last duplicate key:<br>merged map = ${show(mergeMaps(map1, map2))}</div><br>
<div>Set value of pair-wise summated duplicate keys:<br>merged map = ${show(mergeMaps(map1, map2, (value1, value2) => value1 + value2))}</div><br>
<div>Set value of pair-wise difference of duplicate keys:<br>merged map = ${show(mergeMaps(map1, map2, (value1, value2) => value1 - value2))}</div><br>
<div>Set value of pair-wise multiplication of duplicate keys:<br>merged map = ${show(mergeMaps(map1, map2, (value1, value2) => value1 * value2))}</div><br>
<div>Set value of pair-wise quotient of duplicate keys:<br>merged map = ${show(mergeMaps(map1, map2, (value1, value2) => value1 / value2))}</div><br>
<div>Set value of pair-wise power of duplicate keys:<br>merged map = ${show(mergeMaps(map1, map2, (value1, value2) => Math.pow(value1, value2)))}</div><br>
`;
<!DOCTYPE html>
<html>
<head>
<title>Parcel Sandbox</title>
<meta charset="UTF-8" />
</head>
<body>
<div id="app"></div>
<script src="src/index.js">
</script>
</body>
</html>

- 311
- 3
- 3
No, there are no builtin operations for these, but you can easily create them your own:
Map.prototype.assign = function(...maps) {
for (const m of maps)
for (const kv of m)
this.add(...kv);
return this;
};
Set.prototype.concat = function(...sets) {
const c = this.constructor;
let res = new (c[Symbol.species] || c)();
for (const set of [this, ...sets])
for (const v of set)
res.add(v);
return res;
};

- 630,263
- 148
- 957
- 1,375
-
9[Don't extend objects you don't own.](https://stackoverflow.com/questions/14034180/why-is-extending-native-objects-a-bad-practice) – fregante Dec 26 '16 at 08:08
-
2
-
1If everyone does what they want we'll end up with another smoosh debacle – dwelle Feb 17 '19 at 16:57
Example
const mergedMaps = (...maps) => {
const dataMap = new Map([])
for (const map of maps) {
for (const [key, value] of map) {
dataMap.set(key, value)
}
}
return dataMap
}
Usage
const map = mergedMaps(new Map([[1, false]]), new Map([['foo', 'bar']]), new Map([['lat', 1241.173512]]))
Array.from(map.keys()) // [1, 'foo', 'lat']
Transform the sets into arrays, flatten them and finally the constructor will uniqify.
const union = (...sets) => new Set(sets.map(s => [...s]).flat());

- 946
- 1
- 7
- 18
-
Please don't post only code as an answer, but also provide an explanation what your code does and how it solves the problem of the question. Answers with an explanation are usually of higher quality, and are more likely to attract upvotes. – Mark Rotteveel Apr 09 '20 at 13:57
I've created a small snippet to merge any number of Sets using a function in ES6. You can change the "Set" to "Map" get it working with Maps.
const mergeSets = (...args) => {
return new Set(args.reduce((acc, current) => {
return [...acc, ...current];
}, []));
};
const foo = new Set([1, 2, 3]);
const bar = new Set([1, 3, 4, 5]);
mergeSets(foo, bar); // Set(5) {1, 2, 3, 4, 5}
mergeSets(foo, bar, new Set([6])); // Set(6) {1, 2, 3, 4, 5, 6}

- 273
- 4
- 12
A good solution no matter if you have two or more maps to merge is to group them as an Array and use the following:
Array.prototype.merge = function () {
return this.reduce((p, c) => Object.assign(c, p), {});
};

- 501
- 5
- 10
You can use the spread syntax to merge them together:
const map1 = {a: 1, b: 2}
const map2 = {b: 1, c: 2, a: 5}
const mergedMap = {...a, ...b}
=> {a: 5, b: 1, c: 2}

- 239,200
- 50
- 490
- 574

- 374
- 2
- 3
-
3Oops, wrong answer. OPs question is about a `Map` and not about a common object like `{}`. – Simon B. Mar 15 '21 at 22:08