Advanced JavaScript (ES6+)

Rest Parameters and Arguments

13 min Lesson 9 of 40

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:

  1. Write a sum function that adds any number of arguments
  2. Create a filter function that takes a predicate and any number of values
  3. 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!

ES
Edrees Salih
16 hours ago

We are still cooking the magic in the way!