JavaScript Rest Parameters: A Complete Tutorial
Learn how to use JavaScript rest parameters to accept any number of function arguments. Covers the ...rest syntax, combining rest with regular parameters, rest vs the arguments object, rest vs spread operator, and practical variadic function patterns.
Rest parameters let a function accept any number of arguments and collect them into a real array. The syntax uses three dots (...) before a parameter name: function sum(...numbers). Unlike the legacy arguments object, rest parameters produce a proper array with access to map, filter, reduce, and every other array method. They work in both regular and arrow functions, making them the modern standard for variadic functions.
This tutorial covers rest parameter syntax, rules, comparisons with the arguments object and spread operator, and practical patterns you will use in real projects.
Basic Rest Parameter Syntax
The ... before a parameter name collects all remaining arguments into an array:
function sum(...numbers) {
console.log(numbers); // [1, 2, 3, 4, 5]
console.log(Array.isArray(numbers)); // true
return numbers.reduce((total, n) => total + n, 0);
}
console.log(sum(1, 2, 3, 4, 5)); // 15
console.log(sum(10, 20)); // 30
console.log(sum()); // 0When no arguments are passed, the rest parameter is an empty array, not undefined:
function test(...args) {
console.log(args); // []
console.log(args.length); // 0
}
test();Combining Rest with Named Parameters
Rest parameters collect only the arguments that do not match named parameters:
function sendMessage(sender, ...recipients) {
console.log(`From: ${sender}`);
console.log(`To: ${recipients.join(", ")}`);
}
sendMessage("Alice", "Bob", "Charlie", "Diana");
// From: Alice
// To: Bob, Charlie, Dianafunction logEvent(level, timestamp, ...details) {
const formatted = `[${level.toUpperCase()}] ${timestamp}`;
details.forEach((detail) => console.log(`${formatted} - ${detail}`));
}
logEvent("info", "2026-02-05", "Server started", "Port 3000", "Ready");
// [INFO] 2026-02-05 - Server started
// [INFO] 2026-02-05 - Port 3000
// [INFO] 2026-02-05 - ReadyThe Rest Parameter Must Be Last
JavaScript enforces a single rule: the rest parameter must be the final parameter.
// Valid: rest is last
function valid(a, b, ...rest) {}
// SyntaxError: Rest parameter must be last formal parameter
// function invalid(...rest, a, b) {}
// SyntaxError: can't have two rest parameters
// function alsoInvalid(...a, ...b) {}| Pattern | Valid? | Reason |
|---|---|---|
function f(...args) | Yes | Single rest parameter |
function f(a, ...args) | Yes | Rest is last |
function f(a, b, ...args) | Yes | Rest is last |
function f(...args, a) | No | Rest is not last |
function f(...a, ...b) | No | Multiple rest parameters |
Rest Parameters in Arrow Functions
Arrow functions support rest parameters with the same syntax:
const multiply = (...numbers) => {
return numbers.reduce((product, n) => product * n, 1);
};
console.log(multiply(2, 3, 4)); // 24
console.log(multiply(5, 10)); // 50// Concise arrow with rest
const first = (...args) => args[0];
const last = (...args) => args[args.length - 1];
console.log(first(10, 20, 30)); // 10
console.log(last(10, 20, 30)); // 30Arrow Functions Need Rest Parameters
Arrow functions do not have their own arguments object. If you need to access all arguments in an arrow function, rest parameters are the only option. This is one key reason rest parameters have replaced the arguments object in modern JavaScript.
Rest vs the arguments Object
The arguments object is a legacy feature available in regular functions. Rest parameters are the modern replacement:
// Legacy: arguments object
function legacySum() {
let total = 0;
for (let i = 0; i < arguments.length; i++) {
total += arguments[i];
}
return total;
}
// Modern: rest parameters
function modernSum(...numbers) {
return numbers.reduce((total, n) => total + n, 0);
}| Feature | arguments | Rest parameters |
|---|---|---|
| Type | Array-like object | Real Array |
Array.isArray() | false | true |
.map(), .filter(), .reduce() | Not available | Available |
.forEach() | Not available | Available |
| Works in arrow functions | No | Yes |
| Named and descriptive | No (always arguments) | Yes (...items, ...errors) |
| Includes all arguments | Yes (all of them) | Only remaining ones |
| Can destructure | No | Yes |
| ES version | ES1 (1997) | ES6 (2015) |
Converting arguments to a Real Array
When working with legacy code, you may need to convert arguments:
function legacyExample() {
// Method 1: Array.from
const arr1 = Array.from(arguments);
// Method 2: spread into array
const arr2 = [...arguments];
// Method 3: Array.prototype.slice (oldest approach)
const arr3 = Array.prototype.slice.call(arguments);
return arr1; // all three produce the same result
}Rest vs Spread Operator
Rest and spread both use ... but do opposite things:
// REST: collects individual items INTO an array (in function parameters)
function sum(...numbers) {
return numbers.reduce((total, n) => total + n, 0);
}
// SPREAD: expands an array INTO individual items (in function calls)
const values = [1, 2, 3, 4];
console.log(sum(...values)); // 10| Context | Name | Direction | Example |
|---|---|---|---|
| Function parameter | Rest | Collects into array | function f(...args) |
| Function call | Spread | Expands out of array | f(...array) |
| Array literal | Spread | Expands into new array | [...arr1, ...arr2] |
| Object literal | Spread | Expands into new object | { ...obj1, ...obj2 } |
// They work together naturally
function merge(...arrays) {
// rest collects all arguments into one array of arrays
return arrays.reduce((result, arr) => [...result, ...arr], []);
// spread expands each inner array
}
console.log(merge([1, 2], [3, 4], [5, 6]));
// [1, 2, 3, 4, 5, 6]Destructuring with Rest Parameters
Rest syntax works inside destructuring assignments to capture remaining elements:
Array Destructuring with Rest
function processScores(first, ...remaining) {
console.log(`Highest: ${first}`);
console.log(`Others: ${remaining}`);
}
processScores(98, 85, 72, 91);
// Highest: 98
// Others: 85,72,91Object Destructuring with Rest
function processUser({ name, email, ...extras }) {
console.log(`Name: ${name}`);
console.log(`Email: ${email}`);
console.log("Extra fields:", extras);
}
processUser({
name: "Alice",
email: "alice@test.com",
age: 28,
role: "admin",
active: true,
});
// Name: Alice
// Email: alice@test.com
// Extra fields: { age: 28, role: "admin", active: true }Rest Destructuring for Removing Properties
Rest destructuring is a clean way to exclude properties from an object without mutation: const { password, ...safeUser } = user; creates a new object without the password field.
Practical Patterns
Flexible Event Logger
function logEvent(eventName, ...data) {
const timestamp = new Date().toISOString();
const entry = {
event: eventName,
timestamp,
data: data.length === 1 ? data[0] : data,
};
console.log(JSON.stringify(entry, null, 2));
return entry;
}
logEvent("click", { button: "submit", page: "/signup" });
// { event: "click", timestamp: "...", data: { button: "submit", page: "/signup" } }
logEvent("error", 404, "Not Found", "/api/users");
// { event: "error", timestamp: "...", data: [404, "Not Found", "/api/users"] }The pipe pattern above demonstrates returning functions from functions, a powerful technique for composing operations:
function pipe(...fns) {
return function (input) {
return fns.reduce((result, fn) => fn(result), input);
};
}
const processName = pipe(
(name) => name.trim(),
(name) => name.toLowerCase(),
(name) => name.replace(/\s+/g, "-")
);
console.log(processName(" John DOE ")); // "john-doe"Safe Math Operations
function safeMax(...values) {
const numbers = values.filter((v) => typeof v === "number" && !isNaN(v));
if (numbers.length === 0) {
return null;
}
return Math.max(...numbers);
}
console.log(safeMax(3, 1, 4, 1, 5)); // 5
console.log(safeMax(3, "hello", NaN, 7)); // 7
console.log(safeMax()); // nullWrapper Functions
Rest parameters make it easy to create wrapper functions that pass arguments through. This pattern is central to higher-order functions:
function withLogging(fn) {
return function (...args) {
console.log(`Calling ${fn.name} with:`, args);
const result = fn(...args);
console.log(`Result:`, result);
return result;
};
}
function add(a, b) {
return a + b;
}
const loggedAdd = withLogging(add);
loggedAdd(3, 5);
// Calling add with: [3, 5]
// Result: 8Merge Multiple Objects
function mergeObjects(...objects) {
return objects.reduce((merged, obj) => ({ ...merged, ...obj }), {});
}
const defaults = { theme: "light", lang: "en", fontSize: 14 };
const userPrefs = { theme: "dark", fontSize: 16 };
const overrides = { fontSize: 18 };
console.log(mergeObjects(defaults, userPrefs, overrides));
// { theme: "dark", lang: "en", fontSize: 18 }Common Mistakes
Putting Rest Before Other Parameters
// SyntaxError
// function wrong(...args, callback) {}
// Correct: rest must be last
function correct(callback, ...args) {
args.forEach((arg) => callback(arg));
}Confusing Rest and Spread
// This is REST (collecting arguments)
function sum(...nums) {
return nums.reduce((a, b) => a + b, 0);
}
// This is SPREAD (expanding an array)
const numbers = [1, 2, 3];
sum(...numbers); // 6
// NOT rest (this is spread inside an array literal)
const combined = [...numbers, 4, 5]; // [1, 2, 3, 4, 5]Rune AI
Key Insights
...argscreates a real array: usemap,filter,reducedirectly on rest parameters- Rest must be last: it collects all remaining arguments after named parameters
- Rest replaces
arguments: works in arrow functions, is a true array, and has a descriptive name - Rest and spread are opposites: rest collects, spread expands, both use
... - Empty calls produce
[]: rest parameters default to an empty array, notundefined
Frequently Asked Questions
Can a function have more than one rest parameter?
Do rest parameters affect the function's .length property?
Can I use rest parameters with default parameters?
What happens if I use a rest parameter with no extra arguments?
Is there a performance difference between rest and arguments?
Conclusion
Rest parameters collect function arguments into a real array using the ...name syntax. They must be the last parameter in the function definition, produce a true Array instance, and work in both regular and arrow functions. They replace the legacy arguments object, which is array-like but lacks array methods. Rest and spread use the same ... syntax but work in opposite directions: rest collects values into an array, spread expands an array into individual values.
More in this topic
OffscreenCanvas API in JS for UI Performance
Master the OffscreenCanvas API to offload rendering from the main thread. Covers worker-based 2D and WebGL rendering, animation loops inside workers, bitmap transfer, double buffering, chart rendering pipelines, image processing, and performance measurement strategies.
Advanced Web Workers for High Performance JS
Master Web Workers for truly parallel JavaScript execution. Covers dedicated and shared workers, structured cloning, transferable objects, SharedArrayBuffer with Atomics, worker pools, task scheduling, Comlink RPC patterns, module workers, and performance profiling strategies.
JavaScript Macros and Abstract Code Generation
Master JavaScript code generation techniques for compile-time and runtime metaprogramming. Covers AST manipulation, Babel plugin authorship, tagged template literals as macros, code generation pipelines, source-to-source transformation, compile-time evaluation, and safe eval alternatives.