My Logo

PUBLISHED JUNE 14, 2024

Teghen Donald Ticha
Lastly Updated: 3 months ago
Reading time 11 mins

How to Use the Set and Map Objects in JavaScript

Learn how to effectively use JavaScript's Set and Map objects for efficient data management. This guide covers basic and advanced operations, practical examples, and best practices
How to Use the Set and Map Objects in JavaScript

Prerequisite

Before diving into the usage of Set and Map objects in JavaScript, it's helpful to have a basic understanding of the following concepts:

  1. 1.JavaScript Fundamentals: Familiarity with JavaScript syntax and basic programming constructs such as variables, loops, and functions.
  2. 2.Arrays and Objects: Knowledge of how to work with arrays and objects in JavaScript, as they are often used in conjunction with Set and Map objects.

With these prerequisites in place, you'll be well-prepared to explore and follow through smoothly.

Brief Overview of JavaScript Collections

JavaScript provides several types of collections for managing and storing data.

The most common collections are `arrays` and `objects`.

Arrays are ordered collections of values that can be accessed by their index, while objects are collections of key-value pairs where each key is a unique identifier.

While arrays and objects are fundamental and versatile, they have some limitations when it comes to managing unique values or key-value pairs efficiently.

This is where `Set` and `Map` objects come into play, offering more specialized and optimized solutions for these use cases.


Importance of Understanding Set and Map Objects

Understanding `Set` and `Map` objects is crucial for any JavaScript developer aiming to write clean, efficient, and optimized code.

`Set` objects allow for the storage of unique values, eliminating duplicates automatically, making them ideal for tasks such as tracking unique items or ensuring that no value is repeated.

`Map` objects, on the other hand, provide a more flexible and powerful way to handle key-value pairs compared to plain objects.

They allow any type of value to be used as a key, including functions, objects, and primitive values.

This flexibility can simplify the handling of complex data structures and improve the readability and performance of your code.

In this guide, we'll explore the basics of Set and Map objects, including their creation, manipulation, and use cases, helping you leverage these powerful tools in your JavaScript projects.

What is a Set?

A Set is a collection of unique values. Unlike arrays, a Set ensures that no value occurs more than once.

This makes Set ideal for tasks where uniqueness is required, such as filtering duplicates from an array.


Basic Operations with Set
1. Creating a Set

To create a Set, you can use the Set constructor. Here's how you do it:

// Creating an empty Set
const mySet = new Set();

console.log(mySet); // Output: Set(0) {}

// Creating a Set with initial values
const randomArray = ["value1", 1, 2.44, false, null, new Date(), function fn(){}, {a:"a1", b: "b1"}];
const mySetWithValues = new Set([...randomArray, ...randomArray]); // try duplicating the array's content while creating the set.

console.log(mySetWithValues); 
/* Output: Set(9) {
  'value1',
  1,
  2.44,
  false,
  null,
  2024-06-14T04:23:38.316Z,
      [Function: fn],
      { a: 'a1', b: 'b1' },
      8
    }
    */

In the example above, we created an empty Set and a Set initialized with values from randomArray. The newly created Set automatically filters out any duplicate values.



2. Adding Elements

You can add elements to a Set using the `add` method.

NB: If the element already exists, it won't be added again.
const dataArray = ["email", "name", "phone", {lat: 1000, log: -9999}];
const extraDataArray2 = ["age", "address", "name", ];

//create the set
const mySet = new Set(dataArray); 
console.log(mySet); // Output: Set(4) { 'email', 'name', 'phone', { lat: 1000, log: -9999 } }

// add elements to the set
extraDataArray2.forEach(ele => {
  mySet.add(ele);
});
console.log(mySet); // Output: Set(4) { 'email', 'name', 'phone', { lat: 1000, log: -9999 }, 'age', 'address' } 
// NB: "name" from extraDataArray2 isn't duplicated

Here, even though we tried to add the value `name` twice, it only appears once in the Set.



3. Removing Elements

To remove an element from a Set, use the `delete` method.

const dataArray = ["email", "name", "phone", "age", "address", {lat: 1000, log: -9999}];

//create the set
const mySet = new Set(dataArray); 

// log the set after creation
console.log(mySet); // output: Set(6) { 'email', 'name', 'phone', 'age', 'address', { lat: 1000, log: -9999 }}

// remove elements from set
console.log(mySet.delete("play")); // output: false

["phone", "address", "id"].forEach( ele => {
  console.log(mySet.delete(ele) ? ` ${ele} was deleted successfully` : `${ele} doesn\'t exixt in the set.`)
}); 
/* output: 
  phone was deleted successfully
  address was deleted successfully
  id doesn't exixt in the set.
  */

// log the set after removing elements
console.log(mySet); // output: Set(4) { 'email', 'name', 'age', { lat: 1000, log: -9999 } }



4. Checking for Existence

You can check if an element exists in a Set using the `has` method.

const dataArray = ["email", "name", "phone", "age", "address", {lat: 1000, log: -9999}];

//create the set
const mySet = new Set(dataArray); 

// log the set after creation
console.log(mySet); // output: Set(6) { 'email', 'name', 'phone', 'age', 'address', { lat: 1000, log: -9999 }}


// checking for existence
["phone", "fax", "address", "id", {lat: 1000, log: -9999}].forEach( ele => {
  console.log(mySet.has(ele) ? `Set contains ${ele}` : `Set doesn\'t contain ${ele}`)
}); 
/* output: 
  Set contains phone
  Set doesn't contain fax
  Set contains address
  Set doesn't contain id
  Set doesn't contain [object Object]
  */

The `has` method returns true if the element exists in the Set and false otherwise.

NB: {lat: 1000, log: -9999} wasn't found because it a reference to a different object.



5. Iterating Over a Set

You can iterate over the elements of a Set using a `for...of` loop or the `forEach` method.


const dataArray = ["email", "name", "phone", "age", "address", {lat: 1000, log: -9999}];

//create the set
const mySet = new Set(dataArray); 

// log the set after creation
console.log(mySet); // output: Set(6) { 'email', 'name', 'phone', 'age', 'address', { lat: 1000, log: -9999 }}
console.log('\n ************* \n');

// using for...of loop
for (const ele of mySet) {
    console.log(ele);
}
console.log('\n ************* \n');

//using forEach loop
mySet.forEach(ele => {
  console.log(ele);
});
console.log('\n ************* \n');

// using keys and values
for (const ele of mySet.keys()) {
  console.log(ele);
}
console.log('\n ************* \n');

for (const ele of mySet.values()) {
  console.log(ele);
}
console.log('\n ************* \n');

for (const [key, value] of mySet.entries()) {
  console.log(`key: ${key}  && value: ${value}`);
}

All the above methods allow you to traverse the Set and access each element.

NB: When dealing with Set objects, the key and value for each element are the same.



Common Use Cases for Set
1. Removing Duplicates from an Array:

This in probabbly one of the common use case

const numbers = [1, 2, 2, 3, 4, 4, 5];

const uniqueNumbers = [...new Set(numbers)];

console.log(uniqueNumbers); // Output: [1, 2, 3, 4, 5]

By converting an array to a Set and then back to an array, you can remove any duplicate values.



2. Checking for Unique Values:
const names = ['Teghen', 'Yohan', 'Donald', 'Zem', "Donald", "Yohan"];

const uniqueNamesSet = new Set(names);

if (uniqueNamesSet.size !== names.length) {
  console.log('The array contains duplicate names.');
} else {
  console.log('All names are unique.');
}

// Output: The array contains duplicate names.

Using the `size` property of a Set, you can easily determine if there are any duplicate values in an array.



3. Tracking Unique Events:

For this case, let's use node.js event API to illustrate the idea.

const event = require('events');

const ee = new event();
const addEventTracker = new Set();
const emitEventTracker = new Set();

function addUniqueEvent(eventName) {
  if (!addEventTracker.has(eventName)) {
    addEventTracker.add(eventName);
    ee.on(eventName, data => {
      handleUniqueEvents(eventName, data);
    });
    console.log(`Event ${eventName} tracked.`);
  } 
  else {
    console.log(`Event ${eventName} already tracked.`);
  }
}

function handleUniqueEvents(eventName, data) {
  if (!addEventTracker.has(eventName)) {
    console.log(`The event ${eventName} wasn\'t registered!`);
  }
  else {
    if (!emitEventTracker.has(eventName)) {
        console.log(`Data recieved from ${eventName} event :`, data);
        emitEventTracker.add(eventName);
    }
    else {
      console.log(`Event ${eventName} has already been received!`);
    }
  }
}

// create event list and add to addEventTracker set
['greet', 'question1', 'question2', 'questionN'].forEach(ele => {
  addUniqueEvent(ele);
});
console.log('\naddEventTracker set: ', addEventTracker);

// emit events with data
['greeting', 'question1', 'question2', 'questionN'].forEach((ele, index) => {
  setTimeout(() => {
    ee.emit(ele, `data from ${ele}`);
  }, 200 * (index + 1))
});

// delayed the execution for convinience
setTimeout(() => {
  // try registering more event
  ['greeting', 'question1', 'question2'].forEach(ele => {
    addUniqueEvent(ele);
  });
  console.log('\naddEventTracker set: ', addEventTracker);


  // emit same events as above but this time greetings is registered so we should get the log too.
  ['greet', 'greeting', 'question1', 'question2', 'questionN'].forEach((ele, index) => {
    setTimeout(() => {
      ee.emit(ele, `data from ${ele}`);
    }, 200 * (index + 1))
  });
}, 2000);

This example is contrived just for the sake of fun🤓. Run the code above and as expected, you should get something similar to this in the console.

Event greet tracked.
Event question1 tracked.
Event question2 tracked.
Event questionN tracked.

addEventTracker set:  Set(4) { 'greet', 'question1', 'question2', 'questionN' }
Data recieved from question1 event : data from question1
Data recieved from question2 event : data from question2
Data recieved from questionN event : data from questionN
Event greeting tracked.
Event question1 already tracked.
Event question2 already tracked.

addEventTracker set:  Set(5) { 'greet', 'question1', 'question2', 'questionN', 'greeting' }
Data recieved from greet event : data from greet
Data recieved from greeting event : data from greeting
Event question1 has already been received!
Event question2 has already been received!
Event questionN has already been received!

A Set can be used to track unique events or actions, ensuring that each event is only registered and listerned too once.


Another good example could be tracking a unique List of Users. Imagine a chat application where you need to keep track of unique users who have joined a chat room.

Let's setup a ChatRoom class for this.

class ChatRoom {
  constructor() {
    this.users = new Set();
  }

  join(user) {
    if (!this.users.has(user)) {
      this.users.add(user);
      console.log(`${user} joined the chat.`);
    } else {
      console.log(`${user} is already in the chat.`);
    }
  }

  leave(user) {
    if (this.users.has(user)) {
      this.users.delete(user);
      console.log(`${user} left the chat.`);
    } else {
      console.log(`${user} is not in the chat.`);
    }
  }

  listUsers() {
    console.log('Current User list: ', Array.from(this.users));
  }
}

const chatRoom = new ChatRoom();

chatRoom.join('Yohan'); //  Ouput: Yohan joined the chat.
chatRoom.join('Lucky'); // Ouput: Lucky joined the chat.
chatRoom.join('Yohan'); // Ouput: Yohan is already in the chat.
chatRoom.listUsers(); // Ouput: Current User list:  [ 'Yohan', 'Lucky' ]
chatRoom.leave('Lucky'); // Ouput: Lucky left the chat.
chatRoom.join('Trevoh'); // Ouput: Trevoh joined the chat.
chatRoom.listUsers(); // Ouput: Current User list:  [ 'Yohan', 'Trevoh' ]

Here, we use Set to ensure that each visitor is counted only once, providing an accurate count of unique visits.


Performance Considerations

When working with Set, it is important to understand the performance implications:

  • Insertion and Deletion: Operations like adding or deleting elements from a Set have average time complexities of O(1). This makes Set highly efficient for scenarios involving frequent insertions and deletions.
  • Membership Checking: The has method also operates in O(1) time, allowing quick checks for the existence of an element.
  • Iteration: Iterating over a Set is O(n), where n is the number of elements. This is generally fast but can be slower compared to arrays if order matters, as Set maintains insertion order but doesn't support indexed access.

Overall, Set is a powerful and efficient collection for managing unique elements, and understanding its methods, properties, and performance characteristics can help you leverage its full potential in your JavaScript applications.

What is a Map?

A `Map` is a collection of key-value pairs where both keys and values can be of any type.

Unlike objects, which only allow strings and symbols as keys, `Map` objects allow keys of any type, including functions, objects, and primitive types.


Basic Operations with Map
1. Creating a Map

To create a new Map, you can use the `Map` constructor. You can also initialize it with an iterable of key-value pairs.

// Creating an empty Map
const emptyMap = new Map();
console.log(emptyMap); // Output : Map(0) {}

// Creating a Map with initial key-value pairs
const initializedMap = new Map([
  ['key1', 'value1'],
  ['key2', 'value2'],
  ['key3', 'value3']
]);
console.log(initializedMap); // Output: Map { 'key1' => 'value1', 'key2' => 'value2', 'key3' => 'value3' }


2. Setting Key-Value Pairs

You can add key-value pairs to a Map using the `set` method.

const myMap = new Map();

myMap.set('name', 'Yohan');
myMap.set('age', 1);
myMap.set('occupation', 'Enjoyment');

console.log(myMap); // Map(3) { 'name' => 'Yohan', 'age' => 1, 'occupation' => 'Enjoyment' }


3. Getting Values

You can retrieve values from a Map using the `get` method.

const myMap = new Map();

myMap.set('name', 'Yohan');
myMap.set('age', 1);
myMap.set('occupation', 'Pro Crier');

console.log(myMap.get('name')); // Output : Yohan
console.log(myMap.get('age')); // Output : 1
console.log(myMap.get('occupation')); // Output : Pro Crier
console.log(myMap.get('weight')); // Output : undefined


4. Checking for Existence

To check if a key exists in a Map, use the `has` method.

const myMap = new Map();

myMap.set('name', 'Yohan');
myMap.set('age', 1);
myMap.set('occupation', 'Pro Crier');

console.log(myMap.has('name')); // Output : true
console.log(myMap.has('age')); // Output : true
console.log(myMap.has('weight')); // Output : false


5. Removing Key-Value Pairs

You can remove key-value pairs from a Map using the `delete` method.

It returns a true if element exists and is removed, else returns false

const myMap = new Map();

myMap.set('name', 'Yohan');
myMap.set('age', 1);
myMap.set('occupation', 'Pro Crier');

console.log(myMap.delete('occupation')); // Output : true
console.log(myMap.delete('age')); // Output : true

console.log(myMap); // Map(1) { 'name' => 'Yohan' }


To remove all key-value pairs, use the `clear` method.

const myMap = new Map();

myMap.set('name', 'Yohan');
myMap.set('age', 1);
myMap.set('occupation', 'Pro Crier');

console.log(myMap.clear('occupation'));

console.log(myMap); // Output : Map(0) {}


6. Iterating over a Map

Just like with Sets, you can iterate over a Map using various methods such as forEach, for...of, or by using its built-in iterators.

const myMap = new Map();

myMap.set('name', 'Yohan');
myMap.set('age', 1);
myMap.set('occupation', 'Pro Crier');
myMap.set("weight", 8);

// Using forEach
console.log("\n******* using forEach ******");
myMap.forEach((value, key) => {
  console.log(`${key}: ${key === 'weight' ? value + 'kg' : value}`);
});

// Using for...of
console.log("\n******* using for...of ******");
for (const [key, value] of myMap) {
  console.log(`${key}: ${key === 'weight' ? value + 'kg' : value}`);
}

// Using keys, values, and entries iterators
console.log("\n******* using keys method ******");
for (const key of myMap.keys()) {
  console.log(key);
}

console.log("\n******* using values method ******");
for (const value of myMap.values()) {
  console.log(value);
}

console.log("\n******* using entries method ******");
for (const [key, value] of myMap.entries()) {
  console.log(`${key}: ${key === 'weight' ? value + 'kg' : value}`);
}

The result from the above looks something like:

******* using forEach ******
name: Yohan
age: 1
occupation: Pro Crier
weight: 8kg

******* using for...of ******
name: Yohan
age: 1
occupation: Pro Crier
weight: 8kg

******* using keys method ******
name
age
occupation
weight

******* using values method ******
Yohan
1
Pro Crier
8

******* using entries method ******
name: Yohan
age: 1
occupation: Pro Crier
weight: 8kg


Common Use Cases for Map

Maps are particularly useful in scenarios where you need a dictionary-like structure with keys that can be of any type.

There are many use cases where Maps out perform standard objects. Let's look at a use case.

  • Implementing LRU Cache

Let's use a Map to implement a Least Recently Used (`LRU`) cache.

class LRUCache {
  constructor(limit) {
    this.limit = limit;
    this.cache = new Map();
  }

  get(key) {
    if (!this.cache.has(key)) {
      return null
    };
    const value = this.cache.get(key);
    // Move the accessed key to the end to show that it was recently used
    this.cache.delete(key);
    this.cache.set(key, value);
    return value;
  }

  set(key, value) {
    if (this.cache.size >= this.limit) {
      // Remove the first key-value pair (least recently used)
      const firstKey = this.cache.keys().next().value;
      this.cache.delete(firstKey);
    }
    this.cache.set(key, value);
  }
}

const lruCache = new LRUCache(3);
lruCache.set('name', "Yohan");
lruCache.set('age', 1);
lruCache.set('weight', "8kg");

console.log(lruCache.get('name')); // Output: Yohan
lruCache.set("nickName", "Nyucks"); // This should remove 'age' because 'name' was recently accessed

console.log(lruCache.get('age')); // Output: null (Removed!)
console.log(lruCache.get('weight')); // Output: 8kg
console.log(lruCache.get('nickName')); // Output: Nyucks

console.log(lruCache.cache); // Output : Map(3) { 'name' => 'Yohan', 'weight' => '8kg', 'nickName' => 'Nyucks' }



Performance Considerations

When working with Map, it is important to consider its performance characteristics.

1. Time Complexity
  • Insertion and Deletion: The set and delete operations typically have an average time complexity of `O(1)`, making Map very efficient for adding and removing key-value pairs.
  • Access: The get and has methods also have an average time complexity of `O(1)`, ensuring fast access to values.
2. Iteration

Iterating over a Map is generally linear with respect to the number of entries (`O(n)`).

This makes operations like forEach or using iterators efficient even for large collections.

3. Memory Usage

Map can use more memory compared to plain objects, especially when storing a large number of key-value pairs, due to its underlying data structures.

However, the performance benefits often outweigh the additional memory usage.

Set vs Array

Set:

  • Use Case: Use Set when you need to store unique values and quickly check for existence without duplicates.
  • Pros: Automatically handles uniqueness, fast membership testing with has method, supports Mathematical set operations (`union`, `intersection`, etc.).
  • Cons: Ordered iteration is not guaranteed, does not support indexing.



Array:

  • Use Case: Use Array when you need ordered collection of elements with indexed access.
  • Pros: Ordered collection, supports indexing and efficient iteration, wide range of built-in methods (map, filter, reduce).
  • Cons: Checking for existence of an element is slower compared to Set, requires manual handling of duplicates.



Map vs Object

Map:

  • Use Case: Use Map when you need to associate keys with values and the keys can be of different types (including objects).
  • Pros: Efficient key-value pair management, preserves key insertion order, built-in methods for iteration and manipulation (set, get, delete).
  • Cons: More memory overhead compared to plain objects, may be slower for small-scale key access compared to objects.


Object:

  • Use Case: Use plain objects ({}) when you need simple key-value pairs and keys are always strings or symbols.
  • Pros: Lightweight, fast property access and manipulation, widely supported and optimized for common use cases.
  • Cons: Limited to string or symbol keys, does not preserve key insertion order (for-in loop order is not guaranteed), no built-in methods for size or iteration order.



Conclusion

In this comprehensive guide, we've explored JavaScript's Set and Map objects and their functionalities. Here are the key takeaways:

  • Set Objects: Ideal for managing collections of unique values efficiently.
    They offer automatic handling of duplicates, fast membership testing, and support for set operations like union and intersection.
  • Map Objects: Perfect for associating keys with values, especially when keys can be of various types, including objects.
    They preserve insertion order, provide efficient key-value pair management, and offer a range of methods for manipulation and iteration.


Explore Further

To deepen your understanding and mastery of Set and Map objects:

  • Practice: Implement these concepts in your projects to see firsthand how they enhance data management and performance.
  • Explore Further: Dive deeper into advanced operations, performance optimizations, and real-world applications of Set and Map in JavaScript.

By leveraging Set and Map effectively, you can write cleaner, more efficient JavaScript code that meets the demands of modern web development.

This concludes our guide on Set and Map objects in JavaScript.

Happy coding💻🤓!

Interested in exploring more 🤓? Check out these Related Posts.