One place for hosting & domains


      Understanding Default Parameters in JavaScript

      The author selected the COVID-19 Relief Fund to receive a donation as part of the Write for DOnations program.

      In ECMAScript 2015, default function parameters were introduced to the JavaScript language. These allow developers to initialize a function with default values if the arguments are not supplied to the function call. Initializing function parameters in this way will make your functions easier to read and less error-prone, and will provide default behavior for your functions. This will help you avoid errors that stem from passing in undefined arguments and destructuring objects that don’t exist.

      In this article, you will review the difference between parameters and arguments, learn how to use default parameters in functions, see alternate ways to support default parameters, and learn what types of values and expressions can be used as default parameters. You will also run through examples that demonstrate how default parameters work in JavaScript.

      Arguments and Parameters

      Before explaining default function parameters, it is important to know what it is that parameters can default to. Because of this, we will first review the difference between arguments and parameters in a function. If you would like to learn more about this distinction, check out our earlier article in the JavaScript series, How to Define Functions in JavaScript.

      In the following code block, you will create a function that returns the cube of a given number, defined as x:

      // Define a function to cube a number
      function cube(x) {
        return x * x * x

      The x variable in this example is a parameter—a named variable passed into a function. A parameter must always be contained in a variable and must never have a direct value.

      Now take a look at this next code block, which calls the cube function you just created:

      // Invoke cube function

      This will give the following output:



      In this case, 10 is an argument—a value passed to a function when it is invoked. Often the value will be contained in a variable as well, such as in this next example:

      // Assign a number to a variable
      const number = 10
      // Invoke cube function

      This will yield the same result:



      If you do not pass an argument to a function that expects one, the function will implicitly use undefined as the value:

      // Invoke the cube function without passing an argument

      This will return:



      In this case, cube() is trying to calculate the value of undefined * undefined * undefined, which results in NaN, or “not a number”. For more on this, take a look at the number section of Understanding Data Types in JavaScript.

      This automatic behavior can sometimes be a problem. In some cases, you might want the parameter to have a value even if no argument was passed to the function. That’s where the default parameters feature comes in handy, a topic that you will cover in the next section.

      Default Parameter Syntax

      With the addition of default parameters in ES2015, you can now assign a default value to any parameter, which the function will use instead of undefined when called without an argument. This section will first show you how to do this manually, and then will guide you through setting default parameters.

      Without default parameters, you would have to explicitly check for undefined values in order to set defaults, as is shown in this example:

      // Check for undefined manually
      function cube(x) {
        if (typeof x === 'undefined') {
          x = 5
        return x * x * x

      This uses a conditional statement to check if the value has been automatically provided as undefined, then sets the value of x as 5. This will result in the following output:



      In contrast, using default parameters accomplishes the same goal in much less code. You can set a default value to the parameter in cube by assigning it with the equality assignment operator (=), as highlighted here:

      // Define a cube function with a default value
      function cube(x = 5) {
        return x * x * x

      Now when the cube function is invoked without an argument, it will assign 5 to x and return the calculation instead of NaN:

      // Invoke cube function without an argument



      It will still function as intended when an argument is passed, ignoring the default value:

      // Invoke cube function with an argument



      However, one important caveat to note is that the default parameter value will also override an explicit undefined passed as an argument to a function, as demonstrated here:

      // Invoke cube function with undefined

      This will give the calculation with x equal to 5:



      In this case, the default parameter values were calculated, and an explicit undefined value did not override them.

      Now that you have an idea of the basic syntax of default parameters, the next section will show how default parameters work with different data types.

      Default Parameter Data Types

      Any primitive value or object can be used as a default parameter value. In this section, you will see how this flexibility increases the ways in which default parameters can be used.

      First, set parameters using a number, string, boolean, object, array, and null value as a default value. This example will use arrow function syntax:

      // Create functions with a default value for each data type
      const defaultNumber = (number = 42) => console.log(number)
      const defaultString = (string = 'Shark') => console.log(string)
      const defaultBoolean = (boolean = true) => console.log(boolean)
      const defaultObject = (object = { id: 7 }) => console.log(object)
      const defaultArray = (array = [1, 2, 3]) => console.log(array)
      const defaultNull = (nullValue = null) => console.log(nullValue)

      When these functions are invoked without parameters, they will all use the default values:

      // Invoke each function


      42 "Shark" true {id: 7} (3) [1, 2, 3] null

      Note that any object created in a default parameter will be created every time the function is called. One of the common use cases for default parameters is to use this behavior to obtain values out of an object. If you try to destructure or access a value from an object that doesn’t exist, it will throw an error. However, if the default parameter is an empty object, it will simply give you undefined values instead of throwing an error:

      // Define a settings function with a default object
      function settings(options = {}) {
        const { theme, debug } = options
        // Do something with settings

      This will avoid the error caused by destructuring objects that don’t exist.

      Now that you’ve seen how default parameters operate with different data types, the next section will explain how multiple default parameters can work together.

      Using Multiple Default Parameters

      You can use as many default parameters as you want in a function. This section will show you how to do this, and how to use it to manipulate the DOM in a real-world example.

      First, declare a sum() function with multiple default parameters:

      // Define a function to add two values
      function sum(a = 1, b = 2) {
        return a + b

      This will result in the following default calculation:



      Additionally, the value used in a parameter can be used in any subsequent default parameter, from left to right. For example, this createUser function creates a user object userObj as the third parameter, and all the function itself does is return userObj with the first two parameters:

      // Define a function to create a user object using parameters
      function createUser(name, rank, userObj = { name, rank }) {
        return userObj
      // Create user
      const user = createUser('Jean-Luc Picard', 'Captain')

      If you call user here, you will get the following:


      {name: "Jean-Luc Picard", rank: "Captain"}

      It is usually recommended to put all default parameters at the end of a list of parameters, so that you can easily leave off optional values. If you use a default parameter first, you will have to explicitly pass undefined to use the default value.

      Here is an example with the default parameter at the beginning of the list:

      // Define a function with a default parameter at the start of the list
      function defaultFirst(a = 1, b) {
        return a + b

      When calling this function, you would have to call defaultFirst() with two arguments:

      defaultFirst(undefined, 2)

      This would give the following:



      Here is an example with the default parameter at the end of the list:

      // Define a function with a default parameter at the end of the list
      function defaultLast(a, b = 1) {
        return a + b

      This would yield the same value:



      Both functions have the same result, but the one with the default value last allows a much cleaner function call.

      For a real-world example, here is a function that will create a DOM element, and add a text label and classes, if they exist.

      // Define function to create an element
      function createNewElement(tag, text, classNames = []) {
        const el = document.createElement(tag)
        el.textContent = text
        classNames.forEach(className => {
        return el

      You can call the function with some classes in an array:

      const greeting = createNewElement('p', 'Hello!', ['greeting', 'active'])

      Calling greeting will give the following value:


      <p class="greeting active">Hello!</p>

      However, if you leave the classNames array out of the function call, it will still work.

      const greeting2 = createNewElement('p', 'Hello!')

      greeting2 now has the following value:



      In this example, forEach() can be used on an empty array without an issue. If that empty array were not set in the default parameter, you would get the following error:


      VM2673:5 Uncaught TypeError: Cannot read property 'forEach' of undefined at createNewElement (<anonymous>:5:14) at <anonymous>:12:18

      Now that you have seen how multiple default parameters can interact, you can move on to the next section to see how function calls work as default parameters.

      Function Calls as Default Parameters

      In addition to primitives and objects, the result of calling a function can be used as a default parameter.

      In this code block, you will create a function to return a random number, and then use the result as the default parameter value in a cube function:

      // Define a function to return a random number from 1 to 10
      function getRandomNumber() {
        return Math.floor(Math.random() * 10)
      // Use the random number function as a default parameter for the cube function
      function cube(x = getRandomNumber()) {
        return x * x * x

      Now invoking the cube function without a parameter will have potentially different results every time you call it:

      // Invoke cube function twice for two potentially different results

      The output from these function calls will vary:


      512 64

      You can even use built-in methods, like those on the Math object, and use the value returned in one function call as a parameter in another function.

      In the following example, a random number is assigned to x, which is used as the parameter in the cube function you created. The y parameter will then calculate the cube root of the number and check to see if x and y are equal:

      // Assign a random number to x
      // Assign the cube root of the result of the cube function and x to y
      function doesXEqualY(x = getRandomNumber(), y = Math.cbrt(cube(x))) {
        return x === y

      This will give the following:



      A default parameter can even be a function definition, as seen in this example, which defines a parameter as the inner function and returns the function call of parameter:

      // Define a function with a default parameter that is an anonymous function
      function outer(
        parameter = function inner() {
          return 100
      ) {
        return parameter()
      // Invoke outer function



      This inner function will be created from scratch every time the outer function is invoked.


      In this article, you learned what default function parameters are and how to use them. Now you can use default parameters to help keep your functions clean and easy to read. You can also assign empty objects and arrays to parameters upfront to reduce both complexity and lines of code when dealing with situations such as retrieving values from an object or looping through an array.

      If you would like to learn more about JavaScript, check out the homepage for our How To Code in JavaScript series, or browse our How to Code in Node.js series for articles on back-end development.

      Source link

      Understanding Generators in JavaScript

      The author selected the Open Internet/Free Speech Fund to receive a donation as part of the Write for DOnations program.


      In ECMAScript 2015, generators were introduced to the JavaScript language. A generator is a process that can be paused and resumed and can yield multiple values. A generator in JavaScript consists of a generator function, which returns an iterable Generator object.

      Generators can maintain state, providing an efficient way to make iterators, and are capable of dealing with infinite data streams, which can be used to implement infinite scroll on the frontend of a web application, to operate on sound wave data, and more. Additionally, when used with Promises, generators can mimic the async/await functionality, which allows us to deal with asynchronous code in a more straightforward and readable manner. Although async/await is a more prevalent way to deal with common, simple asynchronous use cases, like fetching data from an API, generators have more advanced features that make learning how to use them worthwhile.

      In this article, we’ll cover how to create generator functions, how to iterate over Generator objects, the difference between yield and return inside a generator, and other aspects of working with generators.

      Generator Functions

      A generator function is a function that returns a Generator object, and is defined by the function keyword followed by an asterisk (*), as shown in the following:

      // Generator function declaration
      function* generatorFunction() {}

      Occasionally, you will see the asterisk next to the function name, as opposed to the function keyword, such as function *generatorFunction(). This works the same, but function* is a more widely accepted syntax.

      Generator functions can also be defined in an expression, like regular functions:

      // Generator function expression
      const generatorFunction = function*() {}

      Generators can even be the methods of an object or class:

      // Generator as the method of an object
      const generatorObj = {
        *generatorMethod() {},
      // Generator as the method of a class
      class GeneratorClass {
        *generatorMethod() {}

      The examples throughout this article will use the generator function declaration syntax.

      Note: Unlike regular functions, generators cannot be constructed with the new keyword, nor can they be used in conjunction with arrow functions.

      Now that you know how to declare generator functions, lets look at the iterable Generator objects that they return.

      Generator Objects

      Traditionally, functions in JavaScript run to completion, and calling a function will return a value when it arrives at the return keyword. If the return keyword is omitted, a function will implicitly return undefined.

      In the following code, for example, we declare a sum() function that returns a value that is the sum of two integer arguments:

      // A regular function that sums two values
      function sum(a, b) {
        return a + b

      Calling the function returns a value that is the sum of the arguments:

      const value = sum(5, 6) // 11

      A generator function, however, does not return a value immediately, and instead returns an iterable Generator object. In the following example, we declare a function and give it a single return value, like a standard function:

      // Declare a generator function with a single return value
      function* generatorFunction() {
        return 'Hello, Generator!'

      When we invoke the generator function, it will return the Generator object, which we can assign to a variable:

      // Assign the Generator object to generator
      const generator = generatorFunction()

      If this were a regular function, we would expect generator to give us the string returned in the function. However, what we actually get is an object in a suspended state. Calling generator will therefore give output similar to the following:


      generatorFunction {<suspended>} __proto__: Generator [[GeneratorLocation]]: VM272:1 [[GeneratorStatus]]: "suspended" [[GeneratorFunction]]: ƒ* generatorFunction() [[GeneratorReceiver]]: Window [[Scopes]]: Scopes[3]

      The Generator object returned by the function is an iterator. An iterator is an object that has a next() method available, which is used for iterating through a sequence of values. The next() method returns an object with value and done properties. value represent the returned value, and done indicates whether the iterator has run through all its values or not.

      Knowing this, let’s call next() on our generator and get the current value and state of the iterator:

      // Call the next method on the Generator object

      This will give the following output:


      {value: "Hello, Generator!", done: true}

      The value returned from calling next() is Hello, Generator!, and the state of done is true, because this value came from a return that closed out the iterator. Since the iterator is done, the generator function’s status will change from suspended to closed. Calling generator again will give the following:


      generatorFunction {<closed>}

      As of right now, we’ve only demonstrated how a generator function can be a more complex way to get the return value of a function. But generator functions also have unique features that distinguish them from normal functions. In the next section, we’ll learn about the yield operator and see how a generator can pause and resume execution.

      yield Operators

      Generators introduce a new keyword to JavaScript: yield. yield can pause a generator function and return the value that follows yield, providing a lightweight way to iterate through values.

      In this example, we’ll pause the generator function three times with different values, and return a value at the end. Then we will assign our Generator object to the generator variable.

      // Create a generator function with multiple yields
      function* generatorFunction() {
        yield 'Neo'
        yield 'Morpheus'
        yield 'Trinity'
        return 'The Oracle'
      const generator = generatorFunction()

      Now, when we call next() on the generator function, it will pause every time it encounters yield. done will be set to false after each yield, indicating that the generator has not finished. Once it encounters a return, or there are no more yields encountered in the function, done will flip to true, and the generator will be finished.

      Use the next() method four times in a row:

      // Call next four times

      These will give the following four lines of output in order:


      {value: "Neo", done: false} {value: "Morpheus", done: false} {value: "Trinity", done: false} {value: "The Oracle", done: true}

      Note that a generator does not require a return; if omitted, the last iteration will return {value: undefined, done: true}, as will any subsequent calls to next() after a generator has completed.

      Iterating Over a Generator

      Using the next() method, we manually iterated through the Generator object, receiving all the value and done properties of the full object. However, just like Array, Map, and Set, a Generator follows the iteration protocol, and can be iterated through with for...of:

      // Iterate over Generator object
      for (const value of generator) {

      This will return the following:


      Neo Morpheus Trinity

      The spread operator can also be used to assign the values of a Generator to an array.

      // Create an array from the values of a Generator object
      const values = [...generator]

      This will give the following array:


      (3) ["Neo", "Morpheus", "Trinity"]

      Both spread and for...of will not factor the return into the values (in this case, it would have been 'The Oracle').

      Note: While both of these methods are effective for working with finite generators, if a generator is dealing with an infinite data stream, it won’t be possible to use spread or for...of directly without creating an infinite loop.

      Closing a Generator

      As we’ve seen, a generator can have its done property set to true and its status set to closed by iterating through all its values. There are two additional ways to immediately cancel a generator: with the return() method, and with the throw() method.

      With return(), the generator can be terminated at any point, just as if a return statement had been in the function body. You can pass an argument into return(), or leave it blank for an undefined value.

      To demonstrate return(), we’ll create a generator with a few yield values but no return in the function definition:

      function* generatorFunction() {
        yield 'Neo'
        yield 'Morpheus'
        yield 'Trinity'
      const generator = generatorFunction()

      The first next() will give us 'Neo', with done set to false. If we invoke a return() method on the Generator object right after that, we’ll now get the passed value and done set to true. Any additional call to next() will give the default completed generator response with an undefined value.

      To demonstrate this, run the following three methods on generator:
      generator.return('There is no spoon!')

      This will give the three following results:


      {value: "Neo", done: false} {value: "There is no spoon!", done: true} {value: undefined, done: true}

      The return() method forced the Generator object to complete and to ignore any other yield keywords. This is particularly useful in asynchronous programming when you need to make functions cancelable, such as interrupting a web request when a user wants to perform a different action, as it is not possible to directly cancel a Promise.

      If the body of a generator function has a way to catch and deal with errors, you can use the throw() method to throw an error into the generator. This starts up the generator, throws the error in, and terminates the generator.

      To demonstrate this, we will put a try...catch inside the generator function body and log an error if one is found:

      // Define a generator function with a try...catch
      function* generatorFunction() {
        try {
          yield 'Neo'
          yield 'Morpheus'
        } catch (error) {
      // Invoke the generator and throw an error
      const generator = generatorFunction()

      Now, we will run the next() method, followed by throw():
      generator.throw(new Error('Agent Smith!'))

      This will give the following output:


      {value: "Neo", done: false} Error: Agent Smith! {value: undefined, done: true}

      Using throw(), we injected an error into the generator, which was caught by the try...catch and logged to the console.

      Generator Object Methods and States

      The following table shows a list of methods that can be used on Generator objects:

      Method Description
      next() Returns the next value in a generator
      return() Returns a value in a generator and finishes the generator
      throw() Throws an error and finishes the generator

      The next table lists the possible states of a Generator object:

      Status Description
      suspended Generator has halted execution but has not terminated
      closed Generator has terminated by either encountering an error, returning, or iterating through all values

      yield Delegation

      In addition to the regular yield operator, generators can also use the yield* expression to delegate further values to another generator. When the yield* is encountered within a generator, it will go inside the delegated generator and begin iterating through all the yields until that generator is closed. This can be used to separate different generator functions to semantically organize your code, while still having all their yields be iterable in the right order.

      To demonstrate, we can create two generator functions, one of which will yield* operate on the other:

      // Generator function that will be delegated to
      function* delegate() {
        yield 3
        yield 4
      // Outer generator function
      function* begin() {
        yield 1
        yield 2
        yield* delegate()

      Next, let’s iterate through the begin() generator function:

      // Iterate through the outer generator
      const generator = begin()
      for (const value of generator) {

      This will give the following values in the order they are generated:


      1 2 3 4

      The outer generator yielded the values 1 and 2, then delegated to the other generator with yield*, which returned 3 and 4.

      yield* can also delegate to any object that is iterable, such as an Array or a Map. Yield delegation can be helpful in organizing code, since any function within a generator that wanted to use yield would also have to be a generator.

      Infinite Data Streams

      One of the useful aspects of generators is the ability to work with infinite data streams and collections. This can be demonstrated by creating an infinite loop inside a generator function that increments a number by one.

      In the following code block, we define this generator function and then initiate the generator:

      // Define a generator function that increments by one
      function* incrementer() {
        let i = 0
        while (true) {
          yield i++
      // Initiate the generator
      const counter = incrementer()

      Now, iterate through the values using next():

      // Iterate through the values

      This will give the following output:


      {value: 0, done: false} {value: 1, done: false} {value: 2, done: false} {value: 3, done: false}

      The function returns successive values in the infinite loop while the done property remains false, ensuring that it will not finish.

      With generators, you don’t have to worry about creating an infinite loop, because you can halt and resume execution at will. However, you still have to have caution with how you invoke the generator. If you use spread or for...of on an infinite data stream, you will still be iterating over an infinite loop all at once, which will cause the environment to crash.

      For a more complex example of an infinite data stream, we can create a Fibonacci generator function. The Fibonacci sequence, which continuously adds the two previous values together, can be written using an infinite loop within a generator as follows:

      // Create a fibonacci generator function
      function* fibonacci() {
        let prev = 0
        let next = 1
        yield prev
        yield next
        // Add previous and next values and yield them forever
        while (true) {
          const newVal = next + prev
          yield newVal
          prev = next
          next = newVal

      To test this out, we can loop through a finite number and print the Fibonacci sequence to the console.

      // Print the first 10 values of fibonacci
      const fib = fibonacci()
      for (let i = 0; i < 10; i++) {

      This will give the following:


      0 1 1 2 3 5 8 13 21 34

      The ability to work with infinite data sets is one part of what makes generators so powerful. This can be useful for examples like implementing infinite scroll on the frontend of a web application.

      Passing Values in Generators

      Throughout this article, we’ve used generators as iterators, and we’ve yielded values in each iteration. In addition to producing values, generators can also consume values from next(). In this case, yield will contain a value.

      It’s important to note that the first next() that is called will not pass a value, but will only start the generator. To demonstrate this, we can log the value of yield and call next() a few times with some values.

      function* generatorFunction() {
        return 'The end'
      const generator = generatorFunction()

      This will give the following output:


      100 200 {value: "The end", done: true}

      It is also possible to seed the generator with an initial value. In the following example, we’ll make a for loop and pass each value into the next() method, but pass an argument to the initial function as well:

      function* generatorFunction(value) {
        while (true) {
          value = yield value * 10
      // Initiate a generator and seed it with an initial value
      const generator = generatorFunction(0)
      for (let i = 0; i < 5; i++) {

      We’ll retrieve the value from next() and yield a new value to the next iteration, which is the previous value times ten. This will give the following:


      0 10 20 30 40

      Another way to deal with starting up a generator is to wrap the generator in a function that will always call next() once before doing anything else.

      async/await with Generators

      An asynchronous function is a type of function available in ES6+ JavaScript that makes working with asynchronous data easier to understand by making it appear synchronous. Generators have a more extensive array of capabilities than asynchronous functions, but are capable of replicating similar behavior. Implementing asynchronous programming in this way can increase the flexibility of your code.

      In this section, we will demonstrate an example of reproducing async/await with generators.

      Let’s build an asynchronous function that uses the Fetch API to get data from the JSONPlaceholder API (which provides example JSON data for testing purposes) and logs the response to the console.

      Start out by defining an asynchronous function called getUsers that fetches data from the API and returns an array of objects, then call getUsers:

      const getUsers = async function() {
        const response = await fetch('')
        const json = await response.json()
        return json
      // Call the getUsers function and log the response
      getUsers().then(response => console.log(response))

      This will give JSON data similar to the following:


      [ {id: 1, name: "Leanne Graham" ...}, {id: 2, name: "Ervin Howell" ...}, {id: 3, name": "Clementine Bauch" ...}, {id: 4, name: "Patricia Lebsack"...}, {id: 5, name: "Chelsey Dietrich"...}, ...]

      Using generators, we can create something almost identical that does not use the async/await keywords. Instead, it will use a new function we create and yield values instead of await promises.

      In the following code block, we define a function called getUsers that uses our new asyncAlt function (which we will write later on) to mimic async/await.

      const getUsers = asyncAlt(function*() {
        const response = yield fetch('')
        const json = yield response.json()
        return json
      // Invoking the function
      getUsers().then(response => console.log(response))

      As we can see, it looks almost identical to the async/await implementation, except that there is a generator function being passed in that yields values.

      Now we can create an asyncAlt function that resembles an asynchronous function. asyncAlt has a generator function as a parameter, which is our function that yields the promises that fetch returns. asyncAlt returns a function itself, and resolves every promise it finds until the last one:

      // Define a function named asyncAlt that takes a generator function as an argument
      function asyncAlt(generatorFunction) {
        // Return a function
        return function() {
          // Create and assign the generator object
          const generator = generatorFunction()
          // Define a function that accepts the next iteration of the generator
          function resolve(next) {
            // If the generator is closed and there are no more values to yield,
            // resolve the last value
            if (next.done) {
              return Promise.resolve(next.value)
            // If there are still values to yield, they are promises and
            // must be resolved.
            return Promise.resolve(next.value).then(response => {
              return resolve(
          // Begin resolving promises
          return resolve(

      This will give the same output as the async/await version:


      [ {id: 1, name: "Leanne Graham" ...}, {id: 2, name: "Ervin Howell" ...}, {id: 3, name": "Clementine Bauch" ...}, {id: 4, name: "Patricia Lebsack"...}, {id: 5, name: "Chelsey Dietrich"...}, ...]

      Note that this implementation is for demonstrating how generators can be used in place of async/await, and is not a production-ready design. It does not have error handling set up, nor does it have the ability to pass parameters into the yielded values. Though this method can add flexibility to your code, often async/await will be a better choice, since it abstracts implementation details away and lets you focus on writing productive code.


      Generators are processes that can halt and resume execution. They are a powerful, versatile feature of JavaScript, although they are not commonly used. In this tutorial, we learned about generator functions and generator objects, methods available to generators, the yield and yield* operators, and generators used with finite and infinite data sets. We also explored one way to implement asynchronous code without nested callbacks or long promise chains.

      If you would like to learn more about JavaScript syntax, take a look at our Understanding This, Bind, Call, and Apply in JavaScript and Understanding Map and Set Objects in JavaScript tutorials.

      Source link

      Understanding Map and Set Objects in JavaScript

      The author selected the Open Internet/Free Speech Fund to receive a donation as part of the Write for DOnations program.

      In JavaScript, developers often spend a lot of time deciding the correct data structure to use. This is because choosing the correct data structure can make it easier to manipulate that data later on, saving time and making code easier to comprehend. The two predominant data structures for storing collections of data are Objects and Arrays (a type of object). Developers use Objects to store key/value pairs and Arrays to store indexed lists. However, to give developers more flexibility, the ECMAScript 2015 specification introduced two new types of iterable objects: Maps, which are ordered collections of key/value pairs, and Sets, which are collections of unique values.

      In this article, you will go over the Map and Set objects, what makes them similar or different to Objects and Arrays, the properties and methods available to them, and examples of some practical uses.


      A Map is a collection of key/value pairs that can use any data type as a key and can maintain the order of its entries. Maps have elements of both Objects (a unique key/value pair collection) and Arrays (an ordered collection), but are more similar to Objects conceptually. This is because, although the size and order of entries is preserved like an Array, the entries themselves are key/value pairs like Objects.

      Maps can be initialized with the new Map() syntax:

      const map = new Map()

      This gives us an empty Map:


      Map(0) {}

      Adding Values to a Map

      You can add values to a map with the set() method. The first argument will be the key, and the second argument will be the value.

      The following adds three key/value pairs to map:

      map.set('firstName', 'Luke')
      map.set('lastName', 'Skywalker')
      map.set('occupation', 'Jedi Knight')

      Here we begin to see how Maps have elements of both Objects and Arrays. Like an Array, we have a zero-indexed collection, and we can also see how many items are in the Map by default. Maps use the => syntax to signify key/value pairs as key => value:


      Map(3) 0: {"firstName" => "Luke"} 1: {"lastName" => "Skywalker"} 2: {"occupation" => "Jedi Knight"}

      This example looks similar to a regular object with string-based keys, but we can use any data type as a key with Maps.

      In addition to manually setting values on a Map, we can also initialize a Map with values already. We do this using an Array of Arrays containing two elements that are each key/value pairs, which looks like this:

      [ [ 'key1', 'value1'], ['key2', 'value2'] ]

      Using the following syntax, we can recreate the same Map:

      const map = new Map([
        ['firstName', 'Luke'],
        ['lastName', 'Skywalker'],
        ['occupation', 'Jedi Knight'],

      Note: This example uses trailing commas, also referred to as dangling commas. This is a JavaScript formatting practice in which the final item in a series when declaring a collection of data has a comma at the end. Though this formatting choice can be used for cleaner diffs and easier code manipulation, whether to use it or not is a matter of preference. For more information on trailing commas, see this Trailing Comma article from the MDN web docs.

      Incidentally, this syntax is the same as the result of calling Object.entries() on an Object. This provides a ready-made way to convert an Object to a Map, as shown in the following code block:

      const luke = {
        firstName: 'Luke',
        lastName: 'Skywalker',
        occupation: 'Jedi Knight',
      const map = new Map(Object.entries(luke))

      Alternatively, you can turn a Map back into an Object or an Array with a single line of code.

      The following converts a Map to an Object:

      const obj = Object.fromEntries(map)

      This will result in the following value of obj:


      {firstName: "Luke", lastName: "Skywalker", occupation: "Jedi Knight"}

      Now, let’s convert a Map to an Array:

      const arr = Array.from(map)

      This will result in the following Array for arr:


      [ ['firstName', 'Luke'], ['lastName', 'Skywalker'], ['occupation', 'Jedi Knight'] ]

      Map Keys

      Maps accept any data type as a key, and do not allow duplicate key values. We can demonstrate this by creating a map and using non-string values as keys, as well as setting two values to the same key.

      First, let’s initialize a map with non-string keys:

      const map = new Map()
      map.set('1', 'String one')
      map.set(1, 'This will be overwritten')
      map.set(1, 'Number one')
      map.set(true, 'A Boolean')

      This example will override the first key of 1 with the subsequent one, and it will treat '1' the string and 1 the number as unique keys:


      0: {"1" => "String one"} 1: {1 => "Number one"} 2: {true => "A Boolean"}

      Although it is a common belief that a regular JavaScript Object can already handle Numbers, booleans, and other primitive data types as keys, this is actually not the case, because Objects change all keys to strings.

      As an example, initialize an object with a numerical key and compare the value for a numerical 1 key and a stringified "1" key:

      // Initialize an object with a numerical key
      const obj = { 1: 'One' }
      // The key is actually a string
      obj[1] === obj['1']  // true

      This is why if you attempt to use an Object as a key, it will print out the string object Object instead.

      As an example, create an Object and then use it as the key of another Object:

      // Create an object
      const objAsKey = { foo: 'bar' }
      // Use this object as the key of another object
      const obj = {
        [objAsKey]: 'What will happen?'

      This will yield the following:


      {[object Object]: "What will happen?"}

      This is not the case with Map. Try creating an Object and setting it as the key of a Map:

      // Create an object
      const objAsKey = { foo: 'bar' }
      const map = new Map()
      // Set this object as the key of a Map
      map.set(objAsKey, 'What will happen?')

      The key of the Map element is now the object we created.


      key: {foo: "bar"} value: "What will happen?"

      There is one important thing to note about using an Object or Array as a key: the Map is using the reference to the Object to compare equality, not the literal value of the Object. In JavaScript {} === {} returns false, because the two Objects are not the same two Objects, despite having the same (empty) value.

      That means that adding two unique Objects with the same value will create a Map with two entries:

      // Add two unique but similar objects as keys to a Map
      map.set({}, 'One')
      map.set({}, 'Two')

      This will yield the following:


      Map(2) {{…} => "One", {…} => "Two"}

      But using the same Object reference twice will create a Map with one entry.

      // Add the same exact object twice as keys to a Map
      const obj = {}
      map.set(obj, 'One')
      map.set(obj, 'Two')

      Which will result in the following:


      Map(1) {{…} => "Two"}

      The second set() is updating the same exact key as the first, so we end up with a Map that only has one value.

      Getting and Deleting Items from a Map

      One of the disadvantages of working with Objects is that it can be difficult to enumerate them, or work with all the keys or values. The Map structure, by contrast, has a lot of built-in properties that make working with their elements more direct.

      We can initialize a new Map to demonstrate the following methods and properties: delete(), has(), get(), and size.

      // Initialize a new Map
      const map = new Map([
        ['animal', 'otter'],
        ['shape', 'triangle'],
        ['city', 'New York'],
        ['country', 'Bulgaria'],

      Use the has() method to check for the existence of an item in a map. has() will return a Boolean.

      // Check if a key exists in a Map
      map.has('shark') // false
      map.has('country') // true

      Use the get() method to retrieve a value by key.

      // Get an item from a Map
      map.get('animal') // "otter"

      One particular benefit Maps have over Objects is that you can find the size of a Map at any time, like you can with an Array. You can get the count of items in a Map with the size property. This involves fewer steps than converting an Object to an Array to find the length.

      // Get the count of items in a Map
      map.size // 4

      Use the delete() method to remove an item from a Map by key. The method will return a Boolean—true if an item existed and was deleted, and false if it did not match any item.

      // Delete an item from a Map by key
      map.delete('city') // true

      This will result in the following Map:


      Map(3) {"animal" => "otter", "shape" => "triangle", "country" => "Bulgaria"}

      Finally, a Map can be cleared of all values with map.clear().

      // Empty a Map

      This will yield:


      Map(0) {}

      Keys, Values, and Entries for Maps

      Objects can retrieve keys, values, and entries by using the properties of the Object constructor. Maps, on the other hand, have prototype methods that allow us to get the keys, values, and entries of the Map instance directly.

      The keys(), values(), and entries() methods all return a MapIterator, which is similar to an Array in that you can use for...of to loop through the values.

      Here is another example of a Map, which we can use to demonstrate these methods:

      const map = new Map([
        [1970, 'bell bottoms'],
        [1980, 'leg warmers'],
        [1990, 'flannel'],

      The keys() method returns the keys:



      MapIterator {1970, 1980, 1990}

      The values() method returns the values:



      MapIterator {"bell bottoms", "leg warmers", "flannel"}

      The entries() method returns an array of key/value pairs:



      MapIterator {1970 => "bell bottoms", 1980 => "leg warmers", 1990 => "flannel"}

      Iteration with Map

      Map has a built-in forEach method, similar to an Array, for built-in iteration. However, there is a bit of a difference in what they iterate over. The callback of a Map’s forEach iterates through the value, key, and map itself, while the Array version iterates through the item, index, and array itself.

      // Map 
      Map.prototype.forEach((value, key, map) = () => {})
      // Array
      Array.prototype.forEach((item, index, array) = () => {})

      This is a big advantage for Maps over Objects, as Objects need to be converted with keys(), values(), or entries(), and there is not a simple way to retrieve the properties of an Object without converting it.

      To demonstrate this, let’s iterate through our Map and log the key/value pairs to the console:

      // Log the keys and values of the Map with forEach
      map.forEach((value, key) => {
        console.log(`${key}: ${value}`)

      This will give:


      1970: bell bottoms 1980: leg warmers 1990: flannel

      Since a for...of loop iterates over iterables like Map and Array, we can get the exact same result by destructuring the array of Map items:

      // Destructure the key and value out of the Map item
      for (const [key, value] of map) {
        // Log the keys and values of the Map with for...of
        console.log(`${key}: ${value}`)

      Map Properties and Methods

      The following table shows a list of Map properties and methods for quick reference:

      Properties/Methods Description Returns
      set(key, value) Appends a key/value pair to a Map Map Object
      delete(key) Removes a key/value pair from a Map by key Boolean
      get(key) Returns a value by key value
      has(key) Checks for the presence of an element in a Map by key Boolean
      clear() Removes all items from a Map N/A
      keys() Returns all keys in a Map MapIterator object
      values() Returns all values in a Map MapIterator object
      entries() Returns all keys and values in a Map as [key, value] MapIterator object
      forEach() Iterates through the Map in insertion order N/A
      size Returns the number of items in a Map Number

      When to Use Map

      Summing up, Maps are similar to Objects in that they hold key/value pairs, but Maps have several advantages over objects:

      • Size – Maps have a size property, whereas Objects do not have a built-in way to retrieve their size.
      • Iteration – Maps are directly iterable, whereas Objects are not.
      • Flexibility – Maps can have any data type (primitive or Object) as the key to a value, while Objects can only have strings.
      • Ordered – Maps retain their insertion order, whereas objects do not have a guaranteed order.

      Due to these factors, Maps are a powerful data structure to consider. However, Objects haves some important advantages as well:

      • JSON – Objects work flawlessly with JSON.parse() and JSON.stringify(), two essential functions for working with JSON, a common data format that many REST APIs deal with.
      • Working with a single element – Working with a known value in an Object, you can access it directly with the key without the need to use a method, such as Map’s get().

      This list will help you decide if a Map or Object is the right data structure for your use case.


      A Set is a collection of unique values. Unlike a Map, a Set is conceptually more similar to an Array than an Object, since it is a list of values and not key/value pairs. However, Set is not a replacement for Arrays, but rather a supplement for providing additional support for working with duplicated data.

      You can initialize Sets with the new Set() syntax.

      const set = new Set()

      This gives us an empty Set:


      Set(0) {}

      Items can be added to a Set with the add() method. (This is not to be confused with the set() method available to Map, although they are similar.)

      // Add items to a Set

      Since Sets can only contain unique values, any attempt to add a value that already exists will be ignored.

      set.add('Chopin') // Set will still contain 3 unique values

      Note: The same equality comparison that applies to Map keys applies to Set items. Two objects that have the same value but do not share the same reference will not be considered equal.

      You can also initialize Sets with an Array of values. If there are duplicate values in the array, they will be removed from the Set.

      // Initialize a Set from an Array
      const set = new Set(['Beethoven', 'Mozart', 'Chopin', 'Chopin'])


      Set(3) {"Beethoven", "Mozart", "Chopin"}

      Conversely, a Set can be converted into an Array with one line of code:

      const arr = [...set]


      (3) ["Beethoven", "Mozart", "Chopin"]

      Set has many of the same methods and properties as Map, including delete(), has(), clear(), and size.

      // Delete an item
      set.delete('Beethoven') // true
      // Check for the existence of an item
      set.has('Beethoven') // false
      // Clear a Set
      // Check the size of a Set
      set.size // 0

      Note that Set does not have a way to access a value by a key or index, like Map.get(key) or arr[index].

      Keys, Values, and Entries for Sets

      Map and Set both have keys(), values(), and entries() methods that return an Iterator. However, while each one of these methods have a distinct purpose in Map, Sets do not have keys, and therefore keys are an alias for values. This means that keys() and values() will both return the same Iterator, and entries() will return the value twice. It makes the most sense to only use values() with Set, as the other two methods exist for consistency and cross-compatibility with Map.

      const set = new Set([1, 2, 3])
      // Get the values of a set


      SetIterator {1, 2, 3}

      Iteration with Set

      Like Map, Set has a built-in forEach() method. Since Sets don’t have keys, the first and second parameter of the forEach() callback return the same value, so there is no use case for it outside of compatibility with Map. The parameters of forEach() are (value, key, set).

      Both forEach() and for...of can be used on Set. First, let’s look at forEach() iteration:

      const set = new Set(['hi', 'hello', 'good day'])
      // Iterate a Set with forEach
      set.forEach((value) => console.log(value))

      Then we can write the for...of version:

      // Iterate a Set with for...of
      for (const value of set) {  

      Both of these strategies will yield the following:


      hi hello good day

      Set Properties and Methods

      The following table shows a list of Set properties and methods for quick reference:

      Properties/Methods Description Returns
      add(value) Appends a new item to a Set Set Object
      delete(value) Removes the specified item from a Set Boolean
      has() Checks for the presence of an item in a Set Boolean
      clear() Removes all items from a Set N/A
      keys() Returns all values in a Set (same as values()) SetIterator object
      values() Returns all values in a Set (same as keys()) SetIterator object
      entries() Returns all values in a Set as [value, value] SetIterator object
      forEach() Iterates through the Set in insertion order N/A
      size Returns the number of items in a Set Number

      When to Use Set

      Set is a useful addition to your JavaScript toolkit, particularly for working with duplicate values in data.

      In a single line, we can create a new Array without duplicate values from an Array that has duplicate values.

      const uniqueArray = [ Set([1, 1, 2, 2, 2, 3])] // (3) [1, 2, 3]

      This will give:


      (3) [1, 2, 3]

      Set can be used for finding the union, intersection, and difference between two sets of data. However, Arrays have a significant advantage over Sets for additional manipulation of the data due to the sort(), map(), filter(), and reduce() methods, as well as direct compatibility with JSON methods.


      In this article, you learned that a Map is a collection of ordered key/value pairs, and that a Set is a collection of unique values. Both of these data structures add additional capabilities to JavaScript and simplify common tasks such as finding the length of a key/value pair collection and removing duplicate items from a data set, respectively. On the other hand, Objects and Arrays have been traditionally used for data storage and manipulation in JavaScript, and have direct compatibility with JSON, which continues to make them the most essential data structures, especially for working with REST APIs. Maps and Sets are primarily useful as supporting data structures for Objects and Arrays.

      If you would like to learn more about JavaScript, check out the homepage for our How To Code in JavaScript series, or browse our How to Code in Node.js series for articles on back-end development.

      Source link