Rest Parameters and Arguments
Rest parameters allow you to represent an indefinite number of arguments as an array. This ES6+ feature provides a clean, modern way to create variadic functions (functions that accept any number of arguments), replacing the older arguments object.
Basic Rest Parameters
Rest parameters use the ... syntax to collect all remaining arguments into a real array:
// Basic rest parameters
function sum(...numbers) {
return numbers.reduce((total, num) => total + num, 0);
}
console.log(sum(1, 2, 3)); // 6
console.log(sum(1, 2, 3, 4, 5)); // 15
console.log(sum()); // 0 (empty array)
// Rest parameters are real arrays
function logArgs(...args) {
console.log(Array.isArray(args)); // true
console.log(args.length); // Number of arguments
// Can use array methods
args.forEach((arg, index) => {
console.log(`Argument ${index}: ${arg}`);
});
}
logArgs('a', 'b', 'c');
// true
// 3
// Argument 0: a
// Argument 1: b
// Argument 2: c
Tip: Rest parameters create a real array, unlike the arguments object. This means you can use all array methods like map, filter, reduce, etc., directly without conversion.
Combining Rest with Regular Parameters
You can mix regular parameters with rest parameters. The rest parameter must always be the last parameter:
// First parameter is separate, rest are collected
function greet(greeting, ...names) {
return `${greeting}, ${names.join(' and ')}!`;
}
console.log(greet('Hello', 'Alice')); // "Hello, Alice!"
console.log(greet('Hello', 'Alice', 'Bob', 'Charlie'));
// "Hello, Alice and Bob and Charlie!"
// Multiple regular parameters, then rest
function calculate(operation, multiplier, ...numbers) {
if (operation === 'multiply') {
return numbers.map(n => n * multiplier);
}
return numbers;
}
console.log(calculate('multiply', 2, 1, 2, 3, 4));
// [2, 4, 6, 8]
Important: Rest parameters must be the last parameter in the function signature. You cannot have any parameters after a rest parameter. function bad(...rest, last) {} is invalid!
Rest Parameters vs Arguments Object
Before ES6, we used the arguments object. Rest parameters are a much better alternative:
// Old way - arguments object
function oldSum() {
// arguments is array-like, not a real array
console.log(Array.isArray(arguments)); // false
// Need to convert to array first
const args = Array.prototype.slice.call(arguments);
return args.reduce((total, num) => total + num, 0);
}
// ES6+ way - rest parameters
function newSum(...numbers) {
// Already a real array!
console.log(Array.isArray(numbers)); // true
return numbers.reduce((total, num) => total + num, 0);
}
console.log(oldSum(1, 2, 3)); // 6
console.log(newSum(1, 2, 3)); // 6
// arguments object has issues
function problematic() {
// Arrow functions don't have arguments
const arrow = () => {
console.log(arguments); // Uses parent's arguments (confusing!)
};
arrow();
}
Key Differences:
arguments is array-like but not a real array
arguments contains ALL arguments, rest only collects remaining ones
arguments doesn't work in arrow functions
- Rest parameters have better performance and clearer intent
Variadic Functions
Rest parameters make creating flexible functions much easier:
// Logger that accepts any number of messages
function log(level, ...messages) {
const timestamp = new Date().toISOString();
const formatted = messages.join(' ');
console.log(`[${timestamp}] [${level}] ${formatted}`);
}
log('INFO', 'User', 'logged', 'in');
// [2024-02-08T10:30:00.000Z] [INFO] User logged in
log('ERROR', 'Connection', 'failed');
// [2024-02-08T10:30:01.000Z] [ERROR] Connection failed
// Mathematical operations
function max(...numbers) {
if (numbers.length === 0) return -Infinity;
return Math.max(...numbers);
}
console.log(max(1, 5, 3, 9, 2)); // 9
// String concatenation with separator
function joinWith(separator, ...strings) {
return strings.join(separator);
}
console.log(joinWith(' - ', 'apple', 'banana', 'orange'));
// "apple - banana - orange"
Use Cases for Rest Parameters
// 1. Building API wrappers
function apiCall(endpoint, method = 'GET', ...params) {
const queryString = params
.map(p => `${p.key}=${p.value}`)
.join('&');
return `${method} ${endpoint}?${queryString}`;
}
console.log(
apiCall('/users', 'GET',
{ key: 'page', value: 1 },
{ key: 'limit', value: 10 }
)
);
// "GET /users?page=1&limit=10"
// 2. Creating arrays with specific patterns
function createRange(start, end, ...excludes) {
const range = [];
for (let i = start; i <= end; i++) {
if (!excludes.includes(i)) {
range.push(i);
}
}
return range;
}
console.log(createRange(1, 10, 3, 5, 7));
// [1, 2, 4, 6, 8, 9, 10]
// 3. Event emitter pattern
class EventEmitter {
constructor() {
this.events = {};
}
on(event, callback) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(callback);
}
emit(event, ...args) {
if (this.events[event]) {
this.events[event].forEach(callback => {
callback(...args); // Spread args to callback
});
}
}
}
const emitter = new EventEmitter();
emitter.on('data', (a, b, c) => console.log('Received:', a, b, c));
emitter.emit('data', 1, 2, 3); // Received: 1 2 3
Rest Parameters with Destructuring
You can combine rest parameters with destructuring for powerful patterns:
// Rest in array destructuring within parameters
function processData([first, second, ...rest]) {
console.log('First:', first);
console.log('Second:', second);
console.log('Rest:', rest);
}
processData([1, 2, 3, 4, 5]);
// First: 1
// Second: 2
// Rest: [3, 4, 5]
// Rest in object destructuring within parameters
function createUser({ name, email, ...metadata }) {
return {
credentials: { name, email },
metadata: metadata
};
}
console.log(createUser({
name: 'Alice',
email: 'alice@example.com',
age: 30,
city: 'New York',
country: 'USA'
}));
// {
// credentials: { name: 'Alice', email: 'alice@example.com' },
// metadata: { age: 30, city: 'New York', country: 'USA' }
// }
Practical Examples
// 1. Flexible formatting function
function format(template, ...values) {
return template.replace(/\{\d+\}/g, (match) => {
const index = match.slice(1, -1);
return values[index] !== undefined ? values[index] : match;
});
}
console.log(format('Hello {0}, you have {1} messages', 'Alice', 5));
// "Hello Alice, you have 5 messages"
// 2. Partial application
function multiply(multiplier, ...numbers) {
return numbers.map(n => n * multiplier);
}
const double = (...nums) => multiply(2, ...nums);
const triple = (...nums) => multiply(3, ...nums);
console.log(double(1, 2, 3)); // [2, 4, 6]
console.log(triple(1, 2, 3)); // [3, 6, 9]
// 3. Function composition
function compose(...functions) {
return function(initialValue) {
return functions.reduceRight(
(value, fn) => fn(value),
initialValue
);
};
}
const addOne = x => x + 1;
const double = x => x * 2;
const square = x => x * x;
const compute = compose(square, double, addOne);
console.log(compute(2)); // ((2 + 1) * 2)^2 = 36
// 4. Data validation
function validateRequired(fieldName, ...validators) {
return function(value) {
const errors = [];
if (value === undefined || value === null || value === '') {
errors.push(`${fieldName} is required`);
return errors;
}
for (const validator of validators) {
const error = validator(value);
if (error) errors.push(error);
}
return errors.length > 0 ? errors : null;
};
}
const minLength = min => value =>
value.length < min ? `Must be at least ${min} characters` : null;
const hasNumber = value =>
/\d/.test(value) ? null : 'Must contain a number';
const validatePassword = validateRequired(
'Password',
minLength(8),
hasNumber
);
console.log(validatePassword('abc')); // ["Must be at least 8 characters", "Must contain a number"]
console.log(validatePassword('abcd1234')); // null (valid)
Rest Parameters with Default Values
// Rest parameters have default value of empty array
function collect(...items) {
console.log(items); // Always an array, even if empty
}
collect(); // []
collect(1, 2, 3); // [1, 2, 3]
// Combining default parameters with rest
function createList(title = 'Untitled', ...items) {
return {
title,
items,
count: items.length
};
}
console.log(createList());
// { title: 'Untitled', items: [], count: 0 }
console.log(createList('Shopping', 'Milk', 'Bread', 'Eggs'));
// { title: 'Shopping', items: ['Milk', 'Bread', 'Eggs'], count: 3 }
Performance Considerations
Performance: Rest parameters are highly optimized in modern JavaScript engines. They're generally faster than manually handling the arguments object and more memory-efficient.
Common Pitfalls
// ❌ Rest parameter must be last
function bad(...rest, last) {
// SyntaxError: Rest parameter must be last formal parameter
}
// ✅ Correct placement
function good(first, ...rest) {
console.log(first, rest);
}
// ❌ Only one rest parameter allowed
function alsoBad(...rest1, ...rest2) {
// SyntaxError
}
// ❌ Don't use arguments with rest
function unnecessary(...args) {
console.log(arguments); // Don't do this - use args!
}
// ✅ Just use rest parameter
function better(...args) {
console.log(args); // Clean and clear
}
Practice Exercise:
Task: Create utility functions using rest parameters:
- Write a
sum function that adds any number of arguments
- Create a
filter function that takes a predicate and any number of values
- Build a
curry function that collects arguments until enough are provided
Solution:
// 1. Sum function
function sum(...numbers) {
return numbers.reduce((total, num) => total + num, 0);
}
console.log(sum(1, 2, 3, 4, 5)); // 15
// 2. Filter function
function filter(predicate, ...values) {
return values.filter(predicate);
}
console.log(filter(x => x > 5, 1, 3, 6, 8, 4, 9));
// [6, 8, 9]
// 3. Curry function
function curry(fn, arity = fn.length, ...args) {
return (...newArgs) => {
const allArgs = [...args, ...newArgs];
if (allArgs.length >= arity) {
return fn(...allArgs);
}
return curry(fn, arity, ...allArgs);
};
}
const add = (a, b, c) => a + b + c;
const curriedAdd = curry(add);
console.log(curriedAdd(1)(2)(3)); // 6
console.log(curriedAdd(1, 2)(3)); // 6
console.log(curriedAdd(1)(2, 3)); // 6
Summary
In this lesson, you learned:
- Rest parameters collect remaining arguments into a real array
- Use
...name syntax in function parameters
- Rest parameters must be the last parameter
- They replace the older
arguments object with better functionality
- Perfect for creating variadic functions that accept any number of arguments
- Combine with destructuring and default parameters for powerful patterns
Next Up: In the next lesson, we'll explore Higher-Order Functions and functional programming patterns in JavaScript!