JavaScript Three Dots (…) Operator Made Easy (with Examples)

In JavaScript, the three dots operator (…) means two things:

  1. The spread operator
  2. The rest operator

When used as a spread operator, the three dots operator spreads the elements of an array where zero or more arguments are expected.

A classic example of this is when merging two arrays.

For example:

const boys = ['Bob', 'Charlie'];
const girls = ['Alice', 'Diana'];

const all = [...boys, ...girls];

console.log(all); // ["Bob", "Charlie", "Alice", "Diana"]

Here the three dots operator spreads the elements of the arrays to their places into a new array.

The spread operator can also extract elements of an array in the destructuring process.

For example, given a list of 5 names, let’s assign the first three names to separate variables and the last two names to an array:

const names = ["Alice", "Bob", "Charlie", "David", "Eric"];

let first, second, third, lastTwo;
[first, second, third, ...lastTwo] = names;

console.log(lastTwo); // ["David", "Eric"]

The rest operator can collect the arguments passed to a function as an array.

For instance, let’s create a function that sums up any number of arguments:

function sum(...args) {
    return args.reduce((a, b) => a + b, 0);
}

console.log(sum(1, 2, 3));           // 6
console.log(sum(1));                 // 1
console.log(sum(1, 2, 3, 4, 5, 6));  // 21

At this point, it is important to realize the three dots operator is not only restricted to being used on arrays.

You can use it with other iterable types as well.

In this guide, you learn everything you need to know about the three dots (…) operator in JavaScript.

This includes understanding the:

  • Spread syntax.
  • Rest syntax.
  • What problem do the three dots solve?

In addition, you are going to see useful real-life examples of how to use the three dots operator in your code.

Three Dots (…) in JavaScript

In JavaScript, as of ES6, it has been possible to use the (…) operator when dealing with iterable objects.

The three dots operator serves two useful purposes:

  • The spread syntax
  • The rest syntax

In this section, you are going to familiarize yourself with both of these concepts in detail.

Spread Syntax

The spread syntax makes it possible to spread out the elements of an iterable.

In other words, the spread operator expands the iterable elements to places where zero or more arguments are expected.

When dealing with objects, you can use the spread operator to spread key-value pairs where expected.

Let’s take a look at the syntax of the spread operator in different contexts:

In function calls, the spread operator looks like this:

someFunc(...iterObj); // pass all the elements of iterObj as arguments to someFunc.

With array literals, the spread operator works like this:

["el1", "el2", "el3", ...arr]; // add the elements of arr to the array.

With object literals, the syntax of the spread operator looks like this:

let objCopy = { ...obj }; // Spread all key-value pairs from obj to a new object

Now that you have a basic understanding of the spread syntax, let’s see some examples for each case above.

Examples

In the previous section, you saw how you can use the spread syntax with:

  • Function calls
  • Arrays
  • Objects

To support understanding, let’s see examples of each of these use cases.

Spread Syntax with a Function

Let’s implement a function that accepts three arguments and prints them out.

Instead of passing the arguments as regular arguments, let’s pass them as an array.

To do this, you need to use the spread operator to convert them to regular arguments.

Here is how it looks in the code:

function showCoords(x, y, z) {
    console.log(`x: ${x}, y: ${y}, z: ${z}`);
}

const coords = [2, 1.5, 3.5];

showCoords(...coords); // Prints "x: 2, y: 1.5, z: 3.5"

As you can see, this is quite handy.

When the spread operator did not exist, you would have to use the .apply() method to pass array elements as the arguments to a function.

This looks rather dull compared to the spread operator’s neat syntax:

showCoords.apply(null, coords);

Next, let’s see an example of how you can use the spread syntax when modifying arrays.

Spread Syntax with an Array Literal

The classic example of using the spread operator with arrays is when you want to combine two arrays.

For instance:

const boys = ['Bob', 'Charlie'];
const girls = ['Alice', 'Diana'];

const all = [...boys, ...girls];

console.log(all); // ["Bob", "Charlie", "Alice", "Diana"]

Of course, this is not the only way you can use the spread syntax with arrays.

You can for example embed values to the middle of the array as well.

Spread operator illustration where array values are spread to another
The spread operator pulls the elements apart from one array to another.

For example:

const boys = ['Bob', 'Charlie'];
const girls = ['Alice', 'Diana'];

const all = ["Eric", ...boys, ...girls, "Gabriel"];

console.log(all); // ["Eric", "Bob", "Charlie", "Alice", "Diana", "Gabriel"]

Spread Syntax with an Object Literal

Given two objects, you can merge them using the spread operator:

let obj1 = { test: 'value', x: 10 };
let obj2 = { test: 'other value', y: 20 };

let combined = { ...obj1, ...obj2 };

console.log(combined); // Object { test: "other value", x: 10, y: 20 }

As you can see, the combined object consists of all the unique key-value pairs from the original object.

If a key occurs in both objects, the key-value pair from the last object is used.

Cool!

Now you have a solid understanding of how the spread operator works in JavaScript.

Next, let’s take a look at how the three dots operator is used as the rest operator.

Rest Syntax

The rest syntax makes it possible to create a function that accepts any number of arguments.

The rest syntax uses the three dots operator (…).

Here is what using the rest syntax looks like:

function f(a, b, ...rest) {
    // actions
}

Here you can input any number of arguments after the first and the second one.

Obviously, the rest does not have to be called rest. You can give it any other name similar to how you can name regular arguments in JavaScript.

For example:

function f(a, b, ...moreArgs) {
    // actions
}

Restrictions with the Rest Parameters

There are two restrictions when it comes to using the rest parameters:

  1. There can be only one rest parameter.
  2. The rest parameter has to be the last parameter in the function.

Let’s take a look at these restrictions a bit closer.

1. Only One Rest Parameter Allowed

A JavaScript function can only have a single …rest parameter in the function definition.

For instance, this type of function fails to compile:

function f(...args1, ...args2) {}

2. The Rest Parameter Has to Be the Last Parameter

The rest parameter must be the last parameter in the function definition.

For instance, this does not work:

function f(...rest, x, y, z) {}

But this does:

function f(x, y, z, ...rest) {}

What Problem Does the Rest Parameter Solve?

Before rest parameters were introduced to JavaScript, there was a lot of boilerplate code for converting function arguments to an array.

The only way to access the arguments of a function was via the arguments object.

But this was not an array.

To treat the arguments as an array, you would need to convert the arguments to an array separately. This is not too practical.

Here is a comparison of times before the rest parameters and today:

// BEFORE
// Back in the day, "arguments" could be converted to a normal array by:
function f(a, b) {
  let argsArray = Array.prototype.slice.call(arguments);
  // Alternatively
  // let argsArray = [].slice.call(arguments)
  // let argsArray = Array.from(arguments)

  let firstArg = argsArray.shift();
}

// AFTER
// These days, you do not need to make the conversion thanks to rest parameters
function f(...args) {
  let firstArg = args.shift();
}

This example is quite theoretical. In the examples below you are going to see why this is actually really useful.

Speaking of examples, let’s take a look at a bunch of them.

Examples

Enough for the theory. It is time to get our hands dirty with the rest parameters.

Functions with Any Number of Arguments

As mentioned earlier, you can create a function that takes an arbitrary number of arguments by utilizing the rest syntax.

For example, let’s write a function that sums up any number of arguments using the reduce() method.

function sum(...args) {
    return args.reduce((a, b) => a + b, 0);
}

console.log(sum(1, 2, 3));           // 6
console.log(sum(1));                 // 1
console.log(sum(1, 2, 3, 4, 5, 6));  // 21

This is a perfect example of where it is super useful to have the arguments as an array, to begin with.

Now there is a way to directly operate on the arguments, instead of having to convert them to an array separately.

Let’s see another example of the rest parameters.

Combine Regular Arguments with Rest Parameters

As you learned, the two restrictions on the rest parameters are:

  1. The rest parameters must be the last argument in the function definition.
  2. There can be only one rest parameter in a function definition.

But nothing prevents you from having rest parameters mixed with a regular parameter or parameters.

To demonstrate this, let’s modify the sum() function from the previous example.

This time, let’s name the function accumulate() and make it accept two arguments:

  1. The first argument specifies the operation you want to perform.
  2. The last argument is an arbitrary number of values to operate on.

Here is how it looks in the code:

function accumulate(operation, ...args) {
  if (operation == "sum") {
      return args.reduce((a, b) => a + b, 0);
  } else if (operation == "sub") {
      return args.reduce((a, b) => a - b, 0);
  } else {
      return "No operation";
  }
}

console.log(accumulate("sum", 1, 2, 3));           // 6
console.log(accumulate("nothing", 1));             // No operation
console.log(accumulate("sub", 1, 2, 3, 4, 5, 6));  // -21

Function Arguments as an Array

This example focuses on demonstrating how useful it really is to have rest parameters.

As I mentioned earlier, the rest parameter is of type Array.

This lets you directly operate on the arguments inside the function.

In earlier versions of JavaScript, you have to access the function arguments via a special arguments object.

The problem is the arguments object is not an array.

To make it an array, you have to use boilerplate code to make the conversion.

First, let’s write a JavaScript function that sorts any number of arguments (passed as a rest parameter):

function sort(...args) {
  return args.sort();
}

console.log(sort(5, 3, 6, 2)); // prints 2, 3, 5, 6

This piece of code works perfectly as it directly sorts the arguments as an array.

Now let’s go back in time when there were no rest parameters.

Here is a version of the sort() function that does not use rest parameters:

function sort() {
  return arguments.sort();
}

console.log(sort(5, 3, 6, 2)); // throws a TypeError (arguments.sort is not a function)

As you can see, this piece of code causes an error.

The error is caused by the fact that the arguments object is not an array.

To fix this, you have to convert the arguments to an array in a separate line:

function sort() {
  let args = Array.from(arguments);
  let sortedArgs = args.sort();
  return sortedArgs;
}

console.log(sort(5, 3, 6, 2)); // 2, 3, 5, 6

As you can see, this approach is more verbose than the one with the rest parameters.

At this point, you have a good understanding of how the spread/rest syntaxes work in JavaScript. You have read the theory and seen some basic examples.

Before you go, I want to show you a bunch of real-life applications of the three dots operator in JavaScript.

Three Dots Operator Applications

Let’s finish this tutorial by seeing some neat applications of the spread/rest syntax.

Example 1: HTMLCollection to Array

To perform actions on an HTMLCollection with JavaScript, you need to turn the HTMLCollection into an array.

Because HTMLCollection is an iterable type, you can use a spread operator on it.

Thus, you can use the spread operator to populate an array with the HTMLCollection elements. This converts the HTMLCollection to an array of HTML elements.

For instance:

let elemsArr = [...document.getElementsByTagName("div")];

The elemsArr consists of an array of all the div tags on your page.

Now you can perform actions to the divs in the array without issues.

Example 2: Use Math Functions with Ease

To calculate the max in JavaScript, you can use the Math.max function. To get the minimum, use the Math.min function.

However, these functions behave slightly differently than what you’d expect.

Let me show you what I mean:

let numbers = [1, 4, 2, 10, 6, -1];

let min = Math.min(numbers);
let max = Math.max(numbers);

console.log(min, max); // Prints NaN, NaN

Instead of finding the smallest/largest value, these functions return NaN.

This is because these functions do not expect an iterable argument. Instead, they expect to receive the numbers as separate arguments.

For example:

let min = Math.min(1, 4, 2, 10, 6, -1);
let max = Math.max(1, 4, 2, 10, 6, -1);

console.log(min, max); // -1, 10

Now the functions were successfully able to find the extrema.

But what if you have an array of numbers?

To find the max/min of an array of numbers, use the spread operator.

As you learned, the spread operator takes the array elements and adds them to where zero or more arguments are expected.

For instance:

let numbers = [1, 4, 2, 10, 6, -1];

let min = Math.min(...numbers);
let max = Math.max(...numbers);

console.log(min, max); // Prints -1, 10

Example 3: Destructure Objects

As you already learned, you can use the spread operator for objects as well.

For instance, given an object that represents student data, you can use the spread operator to grab the leftover data to a separate variable.

For instance:

let student = {
  name: "Alice",
  age: 32,
  address: "Imaginary Street 9001",
  allergies: "Peanuts"
}

let {name, age, ...otherInfo} = student;

console.log(name);      // Alice
console.log(age);       // 32
console.log(otherInfo); // { address: "Imaginary Street 9001", allergies: "Peanuts" }
}

Example 4: Take a Shallow Copy of an Array

In JavaScript, the assignment operator (=) creates a reference to the original object.

This can be seen quite easily with a simple example:

let arr1 = [1, 2, 3];
let arr2 = arr1

arr1[0] = 1000;

console.log(arr1); // [1000, 2, 3]
console.log(arr2); // [1000, 2, 3]

Even though we changed the arr1‘s first value, the first value of arr2 changed too.

This is because the arr2 points to the same memory address as arr1.

In other words, you cannot copy an array by assigning it to another.

Instead, you can create a copy of an array by using the spread operator.

For instance:

let arr1 = [1, 2, 3];
let arr2 = [...arr1];

arr1[0] = 1000;

console.log(arr1); // [1000, 2, 3]
console.log(arr2); // [1, 2, 3]

This works because the spread operator picks the elements from the original array and populates a new array with them.

Keep in mind this creates a shallow copy! The copied array elements still reference the original values.

This is not a problem if you deal with numbers.

But if you have an array of arrays changing an element array takes place in both the copied array as well as the original one

Conclusion

Today you learned how to use the three dots operator () in JavaScript.

To recap, the three dots can mean one of two things:

  • The spread operator
  • The rest operator

The spread operator can spread iterable elements out to where one or more arguments are expected.

For instance, you can combine two arrays by spreading the elements of the arrays into one.

The rest operator is useful when working with functions.

It makes it possible to create functions that accept an arbitrary number of arguments.

Also, to receive function arguments as an array directly, you can use the rest parameter.

Before rest parameters, there was a lot of unnecessary code you needed to write to convert function arguments to an array.

Scroll to Top