Write functions with default params, rest parameters, and arrow syntax. Master closures, lexical scoping, recursion, IIFEs, and the arguments object.
Why: default parameters avoid manual null-checks. Rest parameters collect trailing arguments into an array without using the arguments object.
// Default parameters
function greet(name = 'stranger', greeting = 'Hello') {
return `${greeting}, ${name}!`;
}
console.log(greet()); // 'Hello, stranger!'
console.log(greet('Alice')); // 'Hello, Alice!'
console.log(greet('Bob', 'Hi')); // 'Hi, Bob!'
// Rest parameters — collects extra args into an array
function sum(first, ...rest) {
return rest.reduce((acc, n) => acc + n, first);
}
console.log(sum(1, 2, 3, 4)); // 10
// Spread in calls — opposite of rest
const nums = [1, 2, 3];
console.log(Math.max(...nums)); // 3Why: arrow functions are concise and do not have their own this, arguments, or prototype — they inherit this from the surrounding scope.
// Regular function
function square(x) { return x * x; }
// Arrow function — short form
const squareArrow = x => x * x;
console.log(square(4)); // 16
console.log(squareArrow(4)); // 16
// Multi-param, implicit return
const add = (a, b) => a + b;
console.log(add(3, 4)); // 7
// Multi-line body requires explicit return
const describe = (name, age) => {
const born = 2025 - age;
return `${name} was born around ${born}`;
};
console.log(describe('Alice', 30));
// Arrow in array methods
const numbers = [1, 2, 3, 4, 5];
const evens = numbers.filter(n => n % 2 === 0);
console.log(evens); // [2, 4]Why: IIFEs run immediately and create a private scope — useful for one-time initialization. The arguments object is the legacy way to access all function args (prefer rest parameters).
// IIFE — Immediately Invoked Function Expression
const result = (function() {
const x = 10;
const y = 20;
return x + y;
})();
console.log(result); // 30
// Arrow IIFE
const config = (() => ({ version: '1.0', env: 'dev' }))();
console.log(config.version); // '1.0'
// arguments object — only in regular functions (not arrows)
function logAll() {
for (let i = 0; i < arguments.length; i++) {
console.log(arguments[i]);
}
}
logAll('a', 'b', 'c'); // a, b, c
// Prefer rest parameters
function logAllModern(...args) {
args.forEach(a => console.log(a));
}
logAllModern(1, 2, 3);Why: closures let inner functions remember variables from their outer scope even after the outer function has returned — the foundation of encapsulation and data privacy.
// Lexical scoping — inner functions see outer variables
function outer() {
const message = 'hello from outer';
function inner() {
console.log(message); // accesses outer's variable
}
inner();
}
outer();
// Closure — function remembers its birth environment
function makeCounter() {
let count = 0;
return {
increment() { count++; },
decrement() { count--; },
value() { return count; },
};
}
const counter = makeCounter();
counter.increment();
counter.increment();
counter.increment();
counter.decrement();
console.log(counter.value()); // 2
// count is private — inaccessible from outsideWhy: recursion is a function calling itself — elegant for tree traversal, hierarchical data, and divide-and-conquer algorithms. Always define a base case to stop infinite recursion.
// Factorial
function factorial(n) {
if (n <= 1) return 1; // base case
return n * factorial(n - 1); // recursive case
}
console.log(factorial(5)); // 120
console.log(factorial(10)); // 3628800
// Flatten nested array
function flatten(arr) {
return arr.reduce((acc, item) =>
Array.isArray(item)
? [...acc, ...flatten(item)]
: [...acc, item],
[]);
}
console.log(flatten([1, [2, [3, [4]]]])); // [1, 2, 3, 4]
// Fibonacci
function fib(n) {
if (n <= 1) return n;
return fib(n - 1) + fib(n - 2);
}
console.log([0,1,2,3,4,5,6,7].map(fib)); // [0,1,1,2,3,5,8,13]