# Functions

## 1. Four Ways to Create a Function

```js
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) | ❌ |

## 2. Default Parameters

```js
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 NOT
```

Default parameters are evaluated **at call time**, left to right, and can reference earlier parameters:

```js
function makeRange(start, end = start + 10) { return [start, end]; }
makeRange(5); // [5, 15]
```

## 3. Rest Parameters vs `arguments`

```js
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.

## 4. Named Function Arguments via Destructuring

```js
function createUser({ name, age, isAdmin = false }) {
  return { name, age, isAdmin };
}
createUser({ name: "Ana", age: 30 }); // order doesn't matter, defaults still apply
```

This pattern replaces long positional parameter lists and self-documents each call site.

## 5. Function `length` and `name`

```js
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 to
```

## 6. IIFE (Immediately Invoked Function Expression)

```js
(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.

## 7. Pure 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).

```js
// 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.

## 8. Under the Hood: How 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

![Bytecode interpreter warms up to a baseline compiler, then to optimized machine code, with deoptimization falling back](diagrams/01-jit-tiers.svg)

```
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.

```js
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 deopt
```

This 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.

## 9. Common Pitfalls

1. **Using `arguments` inside an arrow function** — arrows have no `arguments` object; they inherit it lexically from the enclosing regular function (or it's undefined at the top level), causing confusing bugs.
2. **`function.length` ignoring rest/default parameters** — don't rely on it to count "how many args this function accepts" once defaults or rest params are involved.
3. **Passing `null` expecting a default parameter to kick in** — defaults only trigger for `undefined`, not `null`.
4. **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).

## 10. 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
```
