45% complete
JavaScript Maps and Sets
Maps and Sets are specialized collection types introduced in ES6 (ECMAScript 2015) that provide more powerful ways to handle collections of data. While arrays and objects can handle most collection needs, Maps and Sets offer unique features that make them better suited for certain scenarios.
When to Use Maps and Sets
Use Maps when you need:
- A collection of key-value pairs where keys can be any data type (not just strings)
- To frequently add and remove key-value pairs
- To easily get the size of the collection
- To maintain insertion order
Use Sets when you need:
- A collection of unique values (no duplicates)
- To quickly check if a value exists
- To easily remove duplicates from an array
- To maintain insertion order
JavaScript Sets
A Set is a collection of unique values. Each value can only occur once in a Set, making it perfect for eliminating duplicates and checking for value existence.
Creating a Set
1// Creating an empty Set2let emptySet = new Set();34// Creating a Set from an array5let colors = new Set(['red', 'green', 'blue', 'red']); // Note: duplicate 'red' is ignored6console.log(colors); // Set(3) {"red", "green", "blue"}78// Creating a Set from a string9let letters = new Set('hello');10console.log(letters); // Set(4) {"h", "e", "l", "o"} (duplicate 'l' is ignored)1112// Adding values during initialization13let numbers = new Set([1, 2, 3, 4, 5]);
Basic Set Operations
1let fruits = new Set();23// Adding values4fruits.add('apple');5fruits.add('banana');6fruits.add('orange');7console.log(fruits); // Set(3) {"apple", "banana", "orange"}89// Adding a duplicate (ignored)10fruits.add('apple');11console.log(fruits); // Still Set(3) {"apple", "banana", "orange"}1213// Checking if a value exists14console.log(fruits.has('banana')); // true15console.log(fruits.has('grape')); // false1617// Getting the size18console.log(fruits.size); // 31920// Removing a value21fruits.delete('orange');22console.log(fruits); // Set(2) {"apple", "banana"}2324// Clearing all values25fruits.clear();26console.log(fruits); // Set(0) {}
Iterating Through a Set
1let colors = new Set(['red', 'green', 'blue']);23// Using forEach4colors.forEach(color => {5 console.log(color);6});7// Output:8// red9// green10// blue1112// Using for...of loop13for (let color of colors) {14 console.log(color);15}16// Output:17// red18// green19// blue2021// Converting Set to Array22let colorsArray = [...colors]; // or Array.from(colors)23console.log(colorsArray); // ["red", "green", "blue"]
Practical Use Cases for Sets
Removing Duplicates
// Remove duplicates from an arraylet numbers = [1, 2, 3, 2, 1, 4, 5, 4];let uniqueNumbers = [...new Set(numbers)];console.log(uniqueNumbers); // [1, 2, 3, 4, 5]
Tracking Unique Visitors
// Track unique user IDslet visitors = new Set();function trackVisitor(userId) {visitors.add(userId);return visitors.size; // Total unique visitors}console.log(trackVisitor("user1")); // 1console.log(trackVisitor("user2")); // 2console.log(trackVisitor("user1")); // 2 (still)
Set Operations (Union, Intersection, Difference)
JavaScript doesn't have built-in methods for set operations like union, intersection, and difference, but we can implement them easily:
1// Set operations2let setA = new Set([1, 2, 3, 4]);3let setB = new Set([3, 4, 5, 6]);45// Union (A ∪ B): all elements from both sets6let union = new Set([...setA, ...setB]);7console.log([...union]); // [1, 2, 3, 4, 5, 6]89// Intersection (A ∩ B): elements that exist in both sets10let intersection = new Set([...setA].filter(x => setB.has(x)));11console.log([...intersection]); // [3, 4]1213// Difference (A - B): elements in A that are not in B14let difference = new Set([...setA].filter(x => !setB.has(x)));15console.log([...difference]); // [1, 2]1617// Symmetric Difference (A △ B): elements in either set but not in both18let symmetricDifference = new Set(19 [...setA].filter(x => !setB.has(x)).concat([...setB].filter(x => !setA.has(x)))20);21console.log([...symmetricDifference]); // [1, 2, 5, 6]
JavaScript Maps
A Map is a collection of key-value pairs where keys can be of any data type. Unlike objects, Maps maintain the insertion order of elements and offer better performance for frequent additions and removals.
Creating a Map
1// Creating an empty Map2let emptyMap = new Map();34// Creating a Map with initial key-value pairs5let userRoles = new Map([6 ['john', 'admin'],7 ['sarah', 'editor'],8 ['mike', 'subscriber']9]);1011console.log(userRoles); // Map(3) {"john" => "admin", "sarah" => "editor", "mike" => "subscriber"}
Basic Map Operations
1let userPreferences = new Map();23// Adding key-value pairs4userPreferences.set('theme', 'dark');5userPreferences.set('fontSize', 16);6userPreferences.set('notifications', true);78console.log(userPreferences);9// Map(3) {"theme" => "dark", "fontSize" => 16, "notifications" => true}1011// Getting values12console.log(userPreferences.get('theme')); // "dark"13console.log(userPreferences.get('fontSize')); // 1614console.log(userPreferences.get('unknown')); // undefined1516// Checking if a key exists17console.log(userPreferences.has('notifications')); // true18console.log(userPreferences.has('language')); // false1920// Getting the size21console.log(userPreferences.size); // 32223// Deleting a key-value pair24userPreferences.delete('notifications');25console.log(userPreferences.has('notifications')); // false2627// Clearing all entries28userPreferences.clear();29console.log(userPreferences.size); // 0
Maps vs. Objects
While Maps and Objects are both collections of key-value pairs, they have important differences:
Feature | Map | Object |
---|---|---|
Key Types | Any value (objects, functions, primitives) | Only strings and symbols |
Size | Available via size property | Manual calculation required |
Iteration | Directly iterable in insertion order | Requires extra steps, order not guaranteed |
Default Keys | No default keys | Has prototype keys that might conflict |
Performance | Better for frequent additions/removals | Better for simple scenarios |
JSON Support | No direct support | Direct support |
Using Non-String Keys
One of the most powerful features of Maps is the ability to use any value as a key, including objects:
1// Using objects as keys2let userObj1 = { id: 1, name: 'John' };3let userObj2 = { id: 2, name: 'Sarah' };45let userScores = new Map();6userScores.set(userObj1, 85);7userScores.set(userObj2, 92);89console.log(userScores.get(userObj1)); // 8510console.log(userScores.get(userObj2)); // 921112// Using functions as keys13function sayHello() { return 'Hello'; }14function sayBye() { return 'Goodbye'; }1516let functionCalls = new Map();17functionCalls.set(sayHello, 0);18functionCalls.set(sayBye, 0);1920// Tracking function calls21function trackCall(fn) {22 let currentCount = functionCalls.get(fn);23 functionCalls.set(fn, currentCount + 1);24 return fn();25}2627trackCall(sayHello); // "Hello"28trackCall(sayHello); // "Hello"29trackCall(sayBye); // "Goodbye"3031console.log(functionCalls.get(sayHello)); // 232console.log(functionCalls.get(sayBye)); // 1
Iterating Through a Map
1let fruitInventory = new Map([2 ['apples', 50],3 ['bananas', 30],4 ['oranges', 25]5]);67// Iterating over key-value pairs8for (let [fruit, count] of fruitInventory) {9 console.log(`${fruit}: ${count}`);10}11// Output:12// apples: 5013// bananas: 3014// oranges: 251516// Using forEach17fruitInventory.forEach((count, fruit) => {18 console.log(`${fruit}: ${count}`);19});20// Output:21// apples: 5022// bananas: 3023// oranges: 252425// Getting all keys26console.log([...fruitInventory.keys()]); // ["apples", "bananas", "oranges"]2728// Getting all values29console.log([...fruitInventory.values()]); // [50, 30, 25]3031// Getting all entries32console.log([...fruitInventory.entries()]); // [["apples", 50], ["bananas", 30], ["oranges", 25]]
Practical Use Cases for Maps
Cache with Object Keys
// Using Map as a cache with complex keysfunction memoize(fn) {const cache = new Map();return function(...args) {const key = JSON.stringify(args);if (cache.has(key)) {console.log('Cache hit!');return cache.get(key);}const result = fn(...args);cache.set(key, result);return result;};}// Expensive calculation functionconst calculateFactorial = memoize(n => {console.log('Calculating factorial...');let result = 1;for (let i = 2; i <= n; i++) {result *= i;}return result;});console.log(calculateFactorial(5)); // Calculatesconsole.log(calculateFactorial(5)); // Uses cache
User Session Tracking
// Tracking user sessionsconst userSessions = new Map();function logUserActivity(userId, activity) {// Get or create user's activity logif (!userSessions.has(userId)) {userSessions.set(userId, []);}// Add timestamp and activityconst activities = userSessions.get(userId);activities.push({timestamp: new Date(),action: activity});return activities.length;}logUserActivity('user123', 'login');logUserActivity('user123', 'view-profile');logUserActivity('user456', 'login');console.log(userSessions.get('user123'));
WeakMap and WeakSet
JavaScript also provides WeakMap and WeakSet, which are versions of Map and Set that don't prevent their keys (WeakMap) or values (WeakSet) from being garbage collected.
WeakMap
A WeakMap is similar to a Map, but:
- Keys must be objects (not primitive values)
- Keys are held "weakly", allowing them to be garbage collected if no other references exist
- It's not iterable and doesn't have size property
- Only has
get
,set
,has
, anddelete
methods
1// Creating a WeakMap2let weakMap = new WeakMap();34// Using objects as keys5let obj1 = { name: 'Object 1' };6let obj2 = { name: 'Object 2' };78weakMap.set(obj1, 'Data for object 1');9weakMap.set(obj2, 'Data for object 2');1011console.log(weakMap.get(obj1)); // "Data for object 1"1213// If we remove all other references to obj114obj1 = null;1516// The entry in weakMap will be automatically removed during garbage collection17// (We can't demonstrate this directly in code as garbage collection timing is unpredictable)
WeakSet
A WeakSet is similar to a Set, but:
- Values must be objects (not primitive values)
- Values are held "weakly", allowing them to be garbage collected
- It's not iterable and doesn't have size property
- Only has
add
,has
, anddelete
methods
1// Creating a WeakSet2let weakSet = new WeakSet();34// Using objects as values5let obj1 = { name: 'Object 1' };6let obj2 = { name: 'Object 2' };78weakSet.add(obj1);9weakSet.add(obj2);1011console.log(weakSet.has(obj1)); // true1213// If we remove all other references to obj114obj1 = null;1516// The value in weakSet will be automatically removed during garbage collection
Use Cases for WeakMap and WeakSet
These are specialized data structures with specific use cases:
1// Using WeakMap for private data2const privateData = new WeakMap();34class User {5 constructor(name, age) {6 this.name = name; // Public property78 // Store "private" data in the WeakMap9 privateData.set(this, { age: age });10 }1112 getAge() {13 return privateData.get(this).age;14 }1516 setAge(age) {17 privateData.get(this).age = age;18 }19}2021let user = new User('John', 30);22console.log(user.name); // "John" (public)23console.log(user.getAge()); // 30 (private)2425// When the user object is no longer referenced,26// its private data will be garbage collected too
When to Use Weak Collections
Use WeakMap and WeakSet when:
- You need to associate data with objects without preventing garbage collection
- You want to store "private" data for objects
- You need to cache results related to objects that might be removed
- You want to avoid memory leaks in long-running applications
Choosing Between Arrays, Objects, Maps, and Sets
Use Case | Best Choice | Why |
---|---|---|
Simple ordered collection of items | Array | Straightforward, indexed access |
Collection of unique values | Set | Automatically handles uniqueness |
Simple key-value pairs with string keys | Object | Lightweight, JSON compatible |
Key-value pairs with non-string keys | Map | Supports any key type |
Frequent additions/removals | Map/Set | Better performance for these operations |
Need to serialize/deserialize | Array/Object | Direct JSON support |
Practice Exercises
Try these exercises to reinforce your understanding of Maps and Sets:
// 1. Create a function that counts the frequency of each word in a string// using a Map. Return the Map sorted by frequency (most frequent first).// 2. Create a function that takes two arrays and returns an array containing// only the elements that appear in both arrays (using Set).// 3. Implement a simple cache system using WeakMap that stores calculation// results for object inputs, but allows objects to be garbage collected// when they're no longer used elsewhere.// 4. Create a function that groups an array of objects by a specified property// using a Map (similar to SQL GROUP BY).// 5. Implement a "unique by property" function that removes objects from an// array if another object has the same value for a specified property.
Summary
In this tutorial, you've learned:
- How to create and use Sets for collections of unique values
- How to create and use Maps for flexible key-value pairs
- The differences between Maps and Objects
- How to perform set operations like union and intersection
- How to iterate through Maps and Sets
- When to use WeakMap and WeakSet for memory-sensitive applications
- How to choose the right collection type for different scenarios
Maps and Sets are powerful additions to JavaScript's collection types. While arrays and objects are still the workhorses of JavaScript data structures, Maps and Sets provide specialized functionality that can make your code more efficient and expressive in certain scenarios.
Related Tutorials
Learn about arrays and how to work with collections of data.
Learn moreLearn about objects and how to work with key-value pairs.
Learn moreLearn how to interact with web page elements using JavaScript.
Learn more