Functions
Default parameters are evaluated at call time, left to right, and can reference earlier parameters:
🎯 What you'll learn
- Four Ways to Create a Function
- Default Parameters
- Rest Parameters vs
arguments - Named Function Arguments via Destructuring
- Function
lengthandname
1Four Ways to Create a Function
function declared(a, b) { return a + b; } // 1. Function declaration — hoisted whole
const expressed = function (a, b) { return a + b; }; // 2. Function expression — not hoisted
const named = function sum(a, b) { return a + b; }; // 3. Named function expression —
// `sum` is only visible INSIDE its own body (useful for recursion)
const arrow = (a, b) => a + b; // 4. Arrow function — no own `this`, no `arguments`, cannot be a constructor| Hoisted fully | Has own this | Has arguments | Can be new'd | |
|---|---|---|---|---|
| Declaration | ✅ | ✅ | ✅ | ✅ |
| Expression | ❌ | ✅ | ✅ | ✅ |
| Arrow | ❌ | ❌ (lexical) | ❌ (lexical) | ❌ |
2Default Parameters
function greet(name = "friend", punctuation = "!") {
return `Hello, ${name}${punctuation}`;
}
greet(); // "Hello, friend!"
greet("Ana"); // "Hello, Ana!"
greet(undefined, "?"); // "Hello, friend?" — undefined triggers the default, but null does NOTDefault parameters are evaluated at call time, left to right, and can reference earlier parameters:
function makeRange(start, end = start + 10) { return [start, end]; }
makeRange(5); // [5, 15]3Rest Parameters vs arguments
function sum(...numbers) { // rest parameter — a REAL array
return numbers.reduce((a, b) => a + b, 0);
}
sum(1, 2, 3); // 6
function legacySum() {
return Array.from(arguments).reduce((a, b) => a + b, 0); // arguments is array-LIKE, not a real array
}Prefer rest parameters (...args) over arguments: they're real arrays (all array methods work directly), they can be combined with named parameters cleanly, and arrow functions don't have arguments at all.
4Named Function Arguments via Destructuring
function createUser({ name, age, isAdmin = false }) {
return { name, age, isAdmin };
}
createUser({ name: "Ana", age: 30 }); // order doesn't matter, defaults still applyThis pattern replaces long positional parameter lists and self-documents each call site.
5Function length and name
function example(a, b, c = 1, ...rest) {}
example.length; // 2 — counts only parameters BEFORE the first default or rest parameter
example.name; // "example"
const anon = function () {};
anon.name; // "anon" — inferred from the variable it's assigned to6IIFE (Immediately Invoked Function Expression)
(function () {
console.log("runs immediately, creates an isolated scope");
})();
(() => {
console.log("arrow IIFE form");
})();Historically used for module-style encapsulation before ES modules existed; still occasionally used to avoid polluting scope in scripts.
7Pure Functions vs Side Effects
A pure function always returns the same output for the same input and causes no observable side effects (no mutation of external state, no I/O).
// Pure
function add(a, b) { return a + b; }
// Impure — depends on and mutates external state
let total = 0;
function addToTotal(n) { total += n; return total; }Pure functions are easier to test, memoize, and reason about — prefer them wherever practical.
Under the HoodHow a Function Actually Gets Executed▾
A function isn't compiled to fast machine code the instant it's defined — engines use a tiered execution pipeline that starts slow and general, then progressively specializes hot functions based on how they're actually observed running.
The tiers, roughly
source code
│ parse
▼
bytecode ──(interpreted by Ignition)──► runs immediately, no compile delay, moderate speed
│ function called often ("warm")
▼
baseline compiled machine code (Sparkplug) ──► faster, still fairly general
│ function called VERY often ("hot"), with STABLE argument/shape patterns
▼
optimizing compiler output (Maglev / TurboFan) ──► highly specialized machine code,
assumes the shapes/types it has seen so far(Tier names above are V8-specific; other engines use different internal names for a broadly similar warm-up pipeline.) Every function starts at the top of this ladder and only climbs higher if it's actually called enough times to be worth the extra compilation cost — a function called once, ever, never needs to be more than bytecode.
Optimized code makes assumptions — and can be thrown away
The optimizing compiler bakes in assumptions based on what it's observed: "this parameter has always been a SMI," "this object has always had hidden class X" (see the Prototypes topic). If a later call violates one of those assumptions, the engine performs a deoptimization ("bailout") — it discards the optimized machine code for that function and falls back to the slower interpreted/baseline tier, at least temporarily, until it can re-specialize.
function add(a, b) { return a + b; }
for (let i = 0; i < 100000; i++) add(1, 2); // called with numbers every time -> gets optimized for numbers
add("1", "2"); // suddenly called with strings -> can trigger a deoptThis is the deeper mechanical reason consistency matters for performance: a function called the same way, with the same argument shapes, every time, is the ideal case for this pipeline. A function called with wildly different argument types call-to-call forces the engine to either generalize its optimized code (slower than the specialized version) or repeatedly deoptimize and re-optimize.
arguments, rest params, and defaults are real bytecode operations
None of section 2-4's conveniences are "magic" — they compile to concrete bytecode instructions executed at function entry: default parameters become a conditional check-and-assign per parameter; rest parameters become a loop that copies the remaining actual arguments into a freshly allocated array object; the legacy arguments object is itself a special array-like object the engine has to construct (historically expensive enough that using it can prevent certain optimizations ...rest doesn't — part of why rest parameters are preferred in modern code).
A function is itself an object
example.length and example.name (section 5) aren't special syntax — they're ordinary, non-enumerable own properties sitting on the function object itself, which has its own hidden class exactly like any other object (Objects topic), plus an internal [[Call]] (and sometimes [[Construct]]) capability that makes it invocable.
⚠️ Common Pitfalls
- Using
argumentsinside an arrow function — arrows have noargumentsobject; they inherit it lexically from the enclosing regular function (or it's undefined at the top level), causing confusing bugs. function.lengthignoring rest/default parameters — don't rely on it to count "how many args this function accepts" once defaults or rest params are involved.- Passing
nullexpecting a default parameter to kick in — defaults only trigger forundefined, notnull. - Forgetting default parameters are re-evaluated every call —
function f(arr = []) {}gives a fresh array every call, not a shared one (this is actually the safe/correct behavior, but surprises people used to Python's mutable-default-argument bug).
📋 Quick Reference
function f(a, b = 1, ...rest) {}
f.length → 1 (params before first default/rest)
...rest → real array of remaining arguments
{a, b} destructure → named-argument style calls
arrow functions → no this, no arguments, not constructible🎤 Interview Q&A
Q1Function declarations vs expressions vs arrow functions?core▾
Declarations are hoisted and named; expressions are assigned to variables (not hoisted as values); arrow functions are concise, have lexical this/arguments, and can't be constructors.
- When not to use arrows? Object methods needing
this, constructors, generators.
Q2What are default, rest and named parameters?core▾
Defaults: function f(a = 1). Rest: function f(...args) gathers remaining args into an array. Named parameters via destructuring an options object: function f({ timeout = 1000, retries = 3 } = {}).
- Why options objects? Readable calls and easy extension.
Q3What are pure functions and why do they matter?core▾
Functions whose output depends only on inputs and which have no side effects. They're easy to test, cache and reason about; React components and reducers rely on purity.
- Side effect examples? Mutating arguments, I/O, Date.now(), random.
Q4What is currying and partial application?senior▾
Currying transforms f(a, b, c) into f(a)(b)(c); partial application fixes some arguments (bind or wrappers). Useful for configuring reusable functions.
- Example?
const log = level => msg => console.log(level, msg).
Q5What is an IIFE and is it still used?core▾
An Immediately Invoked Function Expression (() => { … })() created private scope before modules and block scoping. Today it's mostly used for top-level async code in scripts.
- Modern replacement? ES modules and block scope.
🛠️ Practice exercises
Each exercise is a file in this topic folder. Code exercises are self-checking: run them and make the checks pass.