An Introduction to the Set Data Structure in Javascript

Everything about Set Data Structure in Javascript

There are many situations where you need to compare multiple lists and extract items they have or not in common, available in one only, etc. Sets allow you to do just that and more. In particular, the Javascript Set is a very special and powerful one but still lacks important things that other languages offer.

Video Version of This Article

This post is an improved and more detailed article version of the Set Data Structure Series on Youtube that you can check if you prefer videos.

Watch Videos

What is a Set?

Set is a keyed collection of unique items stored in no particular order. Unlike other collection types like Stack, Queue, and Array, a Set is used in list comparison and to test whether an item exists in a set or not. It is correct to say that a Set stores key-key value pairs because you use the item to test if it is in the set.

Set is also an abstract data type which means that it is defined by its behavior much like Stack and Queue Data Structures. Because of its key-key nature, a Set is much closely related to a Map than anything else and you can even implement a Set using it.

Javascript Set

The Javascript Set is very basic and simple. It lacks common set operation capabilities other languages usually offer. It also uses a unique algorithm to test whether items are the same in comparison to the triple equals (===) strict check.

This means that storing “undefined”, “null” and “NaN” in the set will guarantee they exist only once even though “Nan !== NaN”. Where it shines is in storing object types which are tricky to check equality.

const set = new Set([null, undefined, null, NaN, NaN, null]);console.log([...set]); // [null, undefined, NaN]

You insert with “add” and remove with the “delete” for a single item or the entire set with the “clear” method. It is Iterable and comes with iterators for values and entries since the keys and values are the same. Hence the fact that it is often described as a collection of key-key values pair. It also exposes the “forEach” method like Array and Map as a quicker way to iterate its items.

const set = new Set([78, 20, 44]);set.add(12);
set.delete(78);
set.clear();

Like mentioned before, Set is used to make comparisons and item checks but the only method has that allows you to check something is the “has” method which given an item, it will return true or false whether is in the set or not.

set.has(12); // true
set.has(21); // false

To do more complex checks you must add new methods which can allow you to make powerful comparisons and work with list data nicely which Ill show you later on.

Set vs Array

Set is miles different than Array and the reason they are often compared with each other in Javascript is that Array was often used to do things Set does.

Arrays store items in index order and allow quick read and write of items as long as you know their index. Set allows the same thing as long as you have the item. This shows that Array is an indexed-based collection of items and Set is a key-based collection of items.

The array is meant to be used when you want to keep items in a certain order for access and manipulation. In a Set, items are unique but can repeat inside an Array. Set is often used to clear array items repetition but it is not meant to be used for things Array is for.

const array = [23, 41, 12, 41, 67, 23];const noRepeatArray = Array.from(new Set(array));
// becomes [23, 41, 12, 67]

learn more about Array

Set does not and it is not meant as an Array replacement. It simply addresses a very specific problem of list comparison and item checks that Array is not good for without extensive extra code for handling.

const array = [12, 45];
const set = new Set([12, 45]);
// extra code needed to ensure uniqueness
if(!array.includes(12)) {
array.push(12);
}
// does nothing since it already exists
set.add(12);
// slower: checks every item if necessary
array.includes(45);
// faster: since it checks the key
set.has(45);

When to use Set?

You use Set when all you want is to perform comparison and checks on a specific list. Let's say you have lists A and B and want to know if they are the same. What list A has that B doesn’t or what items they have in common? Also, Set items are unique and it is an excellent property to exploit.

You use it to keep a list of unique items to later iterate and do things so you dont have to worry about the list having repeated items. You use it so when you have another sample list you can find out how they differ or match each other to make certain decisions. You use it so you can later change it to an Array to perform more array-like operations and vice-versa.

Set Operations

In mathematics, whenever you talk about sets there are operations you can perform. As a matter of fact, Set is a computer implementation of the math finite set.

To properly show you Set operations in code, let extend the Javascript Set to inherit its property and methods and give it additional need methods. For the below code we only need a single method that checks if a valid set is provided. For this example, a valid set must be an instance of the Set object and not empty.

class SetExtended extends Set {
#isValidSet = (set) => {
return set && set instanceof Set && set.size > 0;
};
}
  • Union:
    The union operation merges multiple Sets and returns the result of the merge. For the below code implementation, we return a new set by spreading the current and given set in an array to create it.
union(set) {
if (!this.#isValidSet(set)) return new SetExtended();
return new SetExtended([...this, ...set]);
}
  • Intersection:
    The interception operation provides us with a new set containing only the items they have in common. The below example goes over the smaller set (avoids unnecessary checks) and checks if the item exists in the bigger one and adds it to the intersection set then returns it at the end.
intersection(set) {
const intersectionSet = new SetExtended();
if (!this.#isValidSet(set)) return intersectionSet; const [smallerSet, biggerSet] = set.size <= this.size
? [set, this]
: [this, set];
smallerSet.forEach((item) => {
if (biggerSet.has(item)) intersectionSet.add(item);
});
return intersectionSet;
}
  • Difference:
    The difference operation returns a new set containing only the items a set doesn’t have in common with the other. It is also known as Subtraction. The below implementation goes over the current set and collects the items that do not exist in the other into the difference set.
difference(set) {
if (!this.#isValidSet(set)) return new SetExtended();
const differenceSet = new SetExtended(); this.forEach((item) => {
if (!set.has(item)) differenceSet.add(item);
});
return differenceSet;
}
  • Intersection Difference:
    The intersection difference operation is the opposite of the intersection. It is also known as Exclusive Or. It returns a new set containing all the items that they both do not have in common and in the implementation below, we simply create a new set with their differences.
intersectionDifference(set) {
if (!this.#isValidSet(set)) return new SetExtended();
return new SetExtended([
...this.difference(set),
...set.difference(this),
]);
}
  • Subset:
    A set is said to be a subset when all its items are contained by another. The below implementation checks size first because a set cannot be a subset of another if it is bigger and then for every item, it checks if it exists in the other.
isSubsetOf(set) {
if (!this.#isValidSet(set)) return false;
return this.size <= set.size &&
[...this].every(item => set.has(item))
}
  • Superset:
    A superset is the opposite of a subset. A set is a superset when it contains all of the items of another smaller or equal is size set.
isSupersetOf(set) {
if (!this.#isValidSet(set)) return false;
return this.size >= set.size &&
[...set].every(item => this.has(item))
}

You are not limited to these operations and are free to include more that solves the type of need you may have. In general, these checks are super simple to implement and you can always leverage the fact that Set and Array can be converted back and forward to take advantage of powerful array methods.

Source Code: Check this full code on Github

Static Set

A static set is a set that always contains the items it was initialized with. You can’t add, delete or clear its items. The Javascript Set is not static and will always expose the methods that modify the set after its creation. To get a static set we must override this behavior the same way we extended it.

The simplest way is to extend it and override the methods that modify it. That way, you can’t change it later.

class StaticSet extends SetExtended {
constructor(items) {
super(items);

this.add = undefined;
this.delete = undefined;
this.clear = undefined;

}
}

Conclusion

In general, if you find yourself often checking for items existence in lists and filtering items for a certain part of the list it is a clear sign you should explore Sets. The Javascript set is super powerful and easy to work with and probably super underrated.

Check Before Semicolon blog for more Data Structure articles like this. Also, check the Array data structure article for more details.

YouTube Channel: Before Semicolon
Website: beforesemicolon.com

Blog & YouTube Channel for Web, UI & Software Development - beforesemicolon.comyoutube.com/c/BeforeSemicolon

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store