JavaScript's Three Dots (...): Spread and Rest

Last Updated - Sep 23, 2021
Summary : In this article we will learn the basics of using the JavaScript spread operator and rest parameter (...).

Introduction

ECMA6 for JavaScript came out with a lot of useful new features.  The ... syntax is one of these new JavaScript functionalities. It can be used in two different ways, as a spread operator or as a rest parameter.

Rest parameter: collects all remaining elements into an array.

Spread operator: allows iterables (arrays/objects/strings) to be expanded into single arguments/elements.

Rest Parameter

When used inside a function call to replace the arguments or in conjunction with the function arguments, the three dots are considered a rest operator.

The rest operator enables developers to create functions that can take an indefinite number of arguments, also called functions of variable arity or variadic functions.

Let us see how we can use this with the below function:

function add(x, y) {
    return x + y;
}

add(1, 2, 3, 4, 5);  // returns 3

With JavaScript, it is possible to call a function with any number of arguments.  However, the above function only accepts two arguments, so only the first two will be calculated.  So the result of the method call will return 3 since only the first two arguments will be counted. 

Using the rest parameter, we can gather any number of arguments into an array and do what we want with them. 

Let us re-write the add function like this:

function add(...args) {
    let result = 0;
    for (let arg of args) result += arg;
    return result;
}

add(1);  // returns 1
add(1, 2);  //  return 3
add(1, 2, 3, 4, 5);  // returns 15

The rest parameter collects all the remaining arguments into an array.

Note: The rest parameter must be the last argument. This is because it collects all the remaining/excess arguments into an array. 

So having a function declaration like is improper JavaScript and errors out:

function abc (a, ...b, c) {
    ...
    return;
}

We can specify the first arguments separately and the remaining arguments in the function call (regardless of how many there are) will be collected into an array by the rest parameter:

function xyz (x, y, ...z) {
    console.log(x, ' ', y);  // "foo bar"
    console.log(z);  //  ["hello", "hey", "hi", "howdy"]
    console.log(z[0]);  // "hello"
    console.log(z.length);  //  4
}

xyz("foo", "bar", "hello", "hey", "hi", "howdy");

Since the rest parameter gives us an array, we can use JavaScript array methods such as Array.prototype.find.

Arguments Keyword

Before the rest parameter existed, you could use the local variable arguments to get all the arguments passed to non-arrow functions.  However, the arguments variable is not a real array but an array-like object (an object with a length property).

function someFunction() {
    return arguments;
}

someFunction("foo", "bar", 82, false);

In the above example, someFunction returns the arguments and their indexes:

Object {
    0: 'foo',
    1: 'bar',
    2: 82,
    3: false
}

The downside of using the arguments local variable is that it returns an array-like object. This means you basically cannot perform any array-methods like Array.filter or Array.map.  To manipulate this object and use array methods on it, you would have to implement something like Array.prototype.slice.call(arguments, 0). This does not perform well in terms of speed and memory usage and is also not elegant.

Another pitfall is that we cannot use the arguments variable in arrow functions. This is because arrow-functions do not have their own this object, and as a result do not have an arguments object.

Spread Operator

Using the rest parameter we were able to get a list of arguments in an array. The spread operator allows us to expand elements in any iterable into single/individual arguments.

Assume you have the following object:

const person = {
    fullName: 'Foo Bar',
    occupation: 'Software Engineer',
    age: 82,
    website: 'https://techievor.com'
};

Let us say you want to create a new object with a different name and age but same occupation and website.

You can do this by selecting only the properties you want and use the spread operator for the remaining properties, like below:

const newPerson = {
    ...person,
    fullName: 'Rocky Balboa',
    age: 35
};

The above code propagates over the person object and get all its properties, then replaces the existing properties with the properties we passed. Think of the spread operator as a way to extract all the individual properties one by one and transfer them to a new object.

In this case, since we specified the fullName and age properties after the spread operator, the JavaScript engine is smart enough to know that we want to overwrite the original values of those properties that come from the original object.

Array Spread

It’s a similar situation with arrays. Except that instead of propagating over keys and values, the spread operator propagates indexes and values. Unlike object spread, where you cannot have duplicate properties (you cannot have an object with two fullName properties), with arrays you may end up with duplicate values.

This means that the code below will result in you having an array with duplicate elements:

const numbers1 = [1, 2, 3, 4, 5];
//  this results in [1, 2, 3, 4, 5, 1, 2, 6, 7, 8]
const numbers2 = [...numbers1, 1, 2, 6, 7, 8];

The spread operator when used on arrays can be considered as an alternative to Array.prototype.concat.

Note: Unlike the rest parameter, you can use the spread operator as the first argument. 

We can also use the spread operator to copy an array:

const arr = [1, 2, 3];
const arr2 = [...arr];

This copies arr into arr2. Now we can do things on arr2 and any changes done to arr2 will not have any effect arr.

We can also use the spread operator to pass an array as a list of arguments in a function:

function add(a, b, c) {
    return a + b + c;
}

const args = [1, 2, 3];
add(...args);

The add function call is similar to calling the method like add(1, 2, 3).

Conclusion

This should be all you need to know to be productive with the rest parameter and spread operator in JavaScript

... can be used to represent either a spread operator or a rest parameter. How can you tell the difference? It totally depends on how it is used. Given the context in which the three dots are used, it is easy to see if it is being used as a rest parameter or a spread operator

If you have any feedback, or you need help on this concept, please leave a comment below.