# Functions

Source: https://codewiki.com/javascript/functions/

> - **what**: A JavaScript function is a callable first-class value. It binds parameters for one call, executes its body, then returns an explicit result or `undefined`.
> - **trap**: Declarations and expressions initialize at different times; defaults handle only `undefined`, and an ordinary function's `this` also depends on its call form.
> - **fix**: Define inputs, returns, errors, and the callback contract before choosing a function form; test omission, `undefined`, `null`, and detached methods.

## What it is and why it exists

A JavaScript function packages behavior in a callable object. Callers supply data for one execution through arguments, and the function delivers a result through a return value or an explicit side effect. A function isn't syntax fixed to one location: it is a first-class function that you can assign, store in an object, pass as an argument, and return from another function.

Functions solve behavior reuse and boundary definition. An accurately named function can hide how a calculation works behind a stable interface, leaving callers to care about inputs, outputs, and failure modes. It also establishes local scope so temporary names don't leak into surrounding code.

Function declarations, traditional function expressions, and arrow functions all produce function values, but they aren't interchangeable layout choices. Declarations initialize differently; arrows have no own `this` or `arguments` and can't be constructed. Choose a form from its calling contract, not just from which one is shortest.

A parameter appears in a function definition; an argument appears in a call expression. A call binds each argument by position to a parameter, and the counts don't have to match. A missing parameter receives `undefined`; an extra argument matters only if the function chooses to read it.

Functions also make behavior into data. A higher-order function accepts or returns functions so validation, transformation, and policy can be composed without putting every variation in a conditional. A passed function is often called a callback, but the calling API must still define its parameters, return value, call count, and error handling.

You encounter functions in event handling, array transformations, route handlers, scheduled work, test doubles, and dependency injection. Closures, arrow details, and the complete rules for `call`, `apply`, and `bind` have their own topics; this one covers the function foundations shared by all of them.

## How it works

Evaluating function code produces a function object. The object stores executable code and a reference to the lexical environment where it was created, and it has its own identity; matching source text doesn't make two function objects equal. Assigning a function to another variable copies the object reference, not the function body.

A call expression first evaluates the callee and arguments. The runtime then creates an execution context for that call, establishes parameter and local bindings, and determines `this` from the function kind and call form. The call ends when the body reaches `return`, throws, or runs to completion.

```mermaid
flowchart LR
  source["function source"] --> object["function object"]
  object --> invoke["call expression"]
  invoke --> context["execution context"]
  context --> result["return / throw / undefined"]
```

Every `call expression` in the diagram creates fresh parameter and local bindings. A function object can be called repeatedly, but ordinary locals aren't shared between those calls. State that must survive calls belongs in an outside object, a module binding, or a closure.

### Creating functions and binding names

A function declaration creates and initializes its binding when its scope is instantiated, so code in that scope can usually call it before its textual declaration. This is often shortened to "function hoisting," but the important fact is that the binding already holds the function object; the name wasn't merely registered early.

A function expression only produces a function value, while its containing declaration determines when the outside binding can be read. In `const calculate = function () {}`, `calculate` exists in the temporal dead zone from the start of the scope but can't be read until its initializer executes. Saying only that "function expressions aren't hoisted" hides the actual reason for the failure.

A named function expression provides an internal name inside its own body, which supports recursion and better diagnostics. That internal name normally doesn't leak into the outer scope. Anonymous expressions and arrows often infer `name` from their assignment target.

| Form | When the outer name can be called | Own `this` | Constructible with `new` |
| --- | --- | --- | --- |
| `function load() {}` | After scope instantiation | Yes | An ordinary declaration usually is |
| `const load = function () {}` | After the initializer | Yes | An ordinary expression usually is |
| `const load = () => {}` | After the initializer | No | No |

The table describes ordinary functions, not every combination of generators, async functions, methods, and bound functions. Don't guess whether an arbitrary value is constructible from its `prototype` property; when an API requires a constructor, state that contract and test it directly.

### Binding parameters and arguments

At call time, argument expressions are evaluated from left to right and then bound to parameters. Default parameter initializers are also handled from left to right, so a later default can refer to an earlier parameter. An initializer runs for each applicable call, not when the function is defined.

A default applies only when an argument is missing or strictly `undefined`. `null`, `false`, `0`, and the empty string are supplied values and don't trigger a default. If the domain treats `null` as missing too, the function must normalize it explicitly.

A rest parameter collects arguments not already bound into a real array and must come last. It is clearer than legacy `arguments`: its name expresses intent, and array methods work directly. Arrows have no own `arguments`, while the older alias behavior of `arguments` also depends on strict mode and parameter form.

A destructuring parameter first requires its outer value to be destructurable, then processes defaults inside the pattern. `function read({ id }) {}` throws `TypeError` with no argument or `undefined`; `function read({ id } = {}) {}` covers a missing whole object. An inner `id = 'draft'` default handles only a property whose value is `undefined`.

| Call case | Ordinary parameter | Parameter with default | Rest parameter |
| --- | --- | --- | --- |
| Missing argument | `undefined` | Evaluate default expression | Add nothing to the array |
| Argument is `undefined` | `undefined` | Evaluate default expression | Collect `undefined` |
| Argument is `null` | `null` | `null` | Collect `null` |
| Extra argument | No corresponding binding | No corresponding binding | Collect the remaining value |

### Completing a call

`return expression` evaluates the expression and immediately ends the current call. A bare `return` and reaching the end of the body both produce `undefined`. Callers should distinguish a command function that intentionally returns nothing from a function missing its return, even though the runtime values match.

JavaScript inserts semicolons at restricted positions. If a newline immediately follows `return`, that return statement is already complete, and an object literal on the next line isn't returned. Put the opening token of a multiline expression on the same line as `return`, or wrap the expression explicitly in parentheses.

Throwing skips the normal return path and searches up the call stack for a matching handler. A function contract should say which failures are returned and which are thrown; mixing `null`, error objects, and exceptions forces every caller to guess.

Every call to an `async function` returns a Promise, even when its source says `return 3`. An ordinary callback API doesn't start awaiting merely because it receives an async function. The concurrency and propagation rules for asynchronous control flow belong in `javascript/async-await` and `javascript/promises`.

### The call form determines the receiver

An ordinary function's `this` isn't fixed at definition time. `object.method()` supplies the object to the left of the dot as receiver; after extracting the same function and running `detached()`, the receiver rule has changed. A plain ordinary call in strict mode receives `undefined`.

`call`, `apply`, and `bind` can explicitly supply an ordinary function's receiver. An arrow captures `this` from its surroundings, so those methods can't replace an arrow's `this`. If a function doesn't need `this`, pass dependencies as ordinary parameters to make its contract easier to read and test.

An extracted method remains the same function object, but it doesn't remember its original object. APIs such as event subscriptions also remove callbacks by function identity, so creating two temporary wrappers or bound functions fails too. See `javascript/this-binding` and `javascript/call-apply-bind` for the full precedence and repair patterns.

## Examples

These four examples verify name initialization, parameter boundaries, composition of function values, and call-site receivers in sequence. Every output comes from running its file locally with `Node 24`.

### Declaration and expression timing

The `formatOrder` declaration can be called before its source declaration. The `const` binding for `calculateTotal` is still in its temporal dead zone, so the first read throws `ReferenceError`; the same binding works after initialization.

<!-- quick -->

```javascript
// file: declaration_vs_expression.js
console.log(formatOrder('A7'));

function formatOrder(orderId) {
  return `order:${orderId}`;
}

try {
  console.log(calculateTotal([12, 8]));
} catch (error) {
  console.log(error.name);
}

const calculateTotal = function total(lineTotals) {
  return lineTotals.reduce((sum, amount) => sum + amount, 0);
};

console.log(calculateTotal([12, 8]));
console.log(calculateTotal.name);
```

```text
order:A7
ReferenceError
20
total
```


<!-- /quick -->

The `total` output comes from the named function expression's internal name. The outer variable is still `calculateTotal`; the two names serve different purposes: the outside name retrieves the function value, while the inside name supports self-reference and diagnostics.

The error is caught only to show behavior before and after initialization in one run. Production code shouldn't use the temporal dead zone as a branch mechanism; move the declaration, or choose a function declaration when early calls are genuinely required.

### Defaults, destructuring, and rest arguments

`summarizeOrder` provides separate defaults for the whole object parameter and one object property. The rest parameter `amounts` is an array, so an empty input can also reduce safely from the seed `0`.

```javascript
// file: parameter_contracts.js
function summarizeOrder({ id, currency = 'EUR' } = {}, ...amounts) {
  const total = amounts.reduce((sum, amount) => sum + amount, 0);
  return `${id ?? 'draft'}: ${currency} ${total.toFixed(2)}`;
}

const regularOrder = summarizeOrder({ id: 'A-42' }, 10, 5.5);
const omittedCurrency = summarizeOrder({ id: 'B-7', currency: undefined }, 4);
const nullCurrency = summarizeOrder({ id: 'C-9', currency: null }, 4);
const draftOrder = summarizeOrder();

console.log(regularOrder);
console.log(omittedCurrency);
console.log(nullCurrency);
console.log(draftOrder);
```

```text
A-42: EUR 15.50
B-7: EUR 4.00
C-9: null 4.00
draft: EUR 0.00
```

The second call supplies `undefined` explicitly, so the property default still runs. The third preserves `null`, proving that a default parameter isn't a general nullish-coalescing mechanism. If `null` violates the domain contract, validate and reject or normalize it at the boundary.

The whole-object `= {}` protects only omission and `undefined`; passing `null` still can't be destructured. Boundary tests should treat omission, `undefined`, and `null` as three separate cases.

### Passing functions as strategies

`pipe` accepts an initial value and any number of step functions. Each step's return becomes the next step's argument, so the input and output types of adjacent steps must connect.

```javascript
// file: higher_order_pipeline.js
function pipe(value, ...steps) {
  return steps.reduce((current, step) => step(current), value);
}

const addTax = (amount) => amount * 1.2;
const roundCents = (amount) => Math.round(amount * 100) / 100;
const formatEuros = (amount) => `EUR ${amount.toFixed(2)}`;

const total = pipe(80, addTax, roundCents, formatEuros);

console.log(total);
console.log(typeof addTax);
console.log(addTax === ((amount) => amount * 1.2));
```

```text
EUR 96.00
function
false
```

`pipe` is higher-order, while the three steps are ordinary function values. The last line creates a new arrow function object; matching source behavior doesn't give it the same identity as `addTax`.

This small pipeline handles synchronous return values only and lets errors propagate directly. Don't insert asynchronous steps into the same interface without defining Promise, error, and cancellation semantics.

### Preserving a call-site receiver

The same `formatId` function is called first as a method and then as a plain function. In strict mode, the plain call's `this` is `undefined`, so reading `this.id` throws `TypeError`.

```javascript
// file: call_site_receiver.js
'use strict';

function formatId(prefix) {
  return `${prefix}-${this.id}`;
}

const invoice = { id: 7, formatId };
console.log(invoice.formatId('INV'));

const detached = invoice.formatId;
try {
  console.log(detached('INV'));
} catch (error) {
  console.log(error.name);
}

const fixed = detached.bind({ id: 8 });
console.log(fixed('RET'));
```

```text
INV-7
TypeError
RET-8
```

`bind` returns a new function object and permanently stores the receiver supplied here. If code later unregisters this callback, retain that returned value and use the same reference for registration and removal.

If formatting doesn't need method semantics, `formatId(id, prefix)` is a simpler interface. An explicit parameter removes the hidden receiver and lets the function be passed directly to most callback APIs.

## Pitfalls

> **Pitfall:** **Mechanically changing a declaration into a `const` arrow.** Generated refactors often preserve the body while changing initialization timing, `this`, `arguments`, and constructibility; startup code that called the declaration early can fail immediately.

**Fix:** inventory the call sites and the function capabilities they depend on. Replace the form with an arrow only after proving there is no early call, dynamic receiver, `arguments`, or construction path, and run tests for those contracts.

> **Pitfall:** **Treating a default parameter as a default for every empty value.** `value = fallback` runs only for `undefined`; when an API returns `null`, the function keeps it and may later fail in a string or numeric operation.

**Fix:** distinguish "omitted" from "explicitly empty." Use a default parameter when only `undefined` means omission; when both meanings match, normalize nullish input deliberately at the boundary without replacing valid values such as `0`, `false`, or the empty string.

> **Pitfall:** **Forgetting `return` in a block-bodied arrow.** Changing `item => item.id` to `item => { item.id }` makes the braces a function body, not an object or an implicit return, so every call produces `undefined`.

**Fix:** keep an expression body for one expression or write `return` in a larger body. Use `item => ({ id: item.id })` for an object literal, and test the actual return rather than only asserting that the callback ran.

> **Pitfall:** **Treating `arguments` as an array or as an arrow-local value.** `arguments.map(...)` doesn't exist; an arrow reading `arguments` gets it from an outer scope and may silently consume a completely different call.

**Fix:** declare `...args` in new code. A rest parameter gives you a real array and an intentional name, without the alias rules that simple parameters can have in old non-strict code.

> **Pitfall:** **Passing a `this`-dependent method directly as a callback.** `queue.add(service.handle)` passes only a function object, not `service`; the receiver is gone when the later plain call happens.

**Fix:** forward explicitly with `event => service.handle(event)`, or bind once and retain the result. When a listener must be removed, registration and removal need the same function identity.

<!-- deep -->

## Default parameters have an initialization environment

A non-simple parameter list contains a default, destructuring, or a rest parameter. The specification establishes an environment for parameter initialization before entering the lexical declaration environment of the body. This distinction explains several edge cases that "local scope" alone doesn't predict.

A later default initializer can read an earlier initialized parameter, as in `function range(start, end = start) {}`. An earlier initializer can't read a later parameter that is still in its temporal dead zone. A default can call an outer binding, but it can't read a `let`, `const`, or function declaration that exists only in the function body.

Every applicable call reevaluates its default expression. `function collect(items = []) {}` therefore creates a new array for each call that omits the argument; it doesn't share one definition-time container across calls as some languages do. If callers explicitly pass the same array, that aliasing still belongs to the callers.

A default initializer can run arbitrary JavaScript, including function calls and side effects. Complex initialization makes ordering and failure points hard to see. Validation, logging, or asynchronous preparation is usually easier to review when the body performs it in explicit steps.

A destructuring pattern has two levels of missing-value handling. An outer default chooses the object when the whole argument is `undefined`; a property default chooses a value when that property is `undefined`. Neither level automatically accepts `null` or validates the property's type.

## `name` and `length` are descriptive

A function object's `name` often comes from an explicit name, but it can also be inferred from an assignment target, property definition, or default export. It helps stacks and diagnostics, yet minifiers, wrappers, and binding can all change it. Business logic must not choose permissions, routes, or serialization formats from `function.name`.

A function's `length` is the number of parameters before its first default; a rest parameter doesn't count. It isn't the number of required arguments and knows nothing about runtime validation, destructured properties, or TypeScript types. Capability detection based on it commonly misclassifies callbacks.

| Definition | Typical `name` | `length` |
| --- | --- | --- |
| `function save(a, b) {}` | `save` | `2` |
| `const save = function (a, b = 0) {}` | `save` | `1` |
| `const save = (...items) => {}` | `save` | `0` |
| `function save({ id }) {}` | `save` | `1` |

Own property descriptors and other built-in function properties have more detail, but public APIs shouldn't depend on that heuristic metadata. A framework that needs injection, validation, or routing metadata should use explicit configuration, stable markers, or a well-defined wrapper.

## Callable does not mean constructible

Every function value discussed here supports ordinary call syntax, but not every function implements construction. Ordinary declarations and traditional function expressions are generally constructible; arrows and method definitions aren't. Async functions and generators aren't ordinary constructors either.

A construction call creates a new object and supplies it as `this` inside an ordinary constructor body. If the constructor explicitly returns an object, that object can replace the newly created result; returning a primitive doesn't replace it. This special return rule belongs to constructor contracts and shouldn't be mixed into ordinary business functions.

Don't probe an arbitrary function's constructibility on a production path by catching `TypeError`, and don't treat an own `prototype` property as a complete answer. Cases such as bound functions separate surface properties from target capabilities. An API that requires constructors should accept explicitly registered constructors and execute the expected construction path in tests.

Class syntax makes construction intent clearer, and a class can't be called normally without `new`. When you need instance methods, inheritance, or several state operations, a class is usually clearer than an old-style function serving simultaneously as ordinary function and constructor.

## Choosing a function form

Choose a function form by deciding name lifetime, receiver, and construction capability first. Style rules come only after those semantic constraints. Normalizing every function into one form removes contract signals that readers could otherwise see in the syntax.

| Requirement | Useful starting form | Boundary to confirm |
| --- | --- | --- |
| Allow early calls in the same scope | Function declaration | Whether that initialization timing is intentional |
| Initialize behavior as a value at one point | Function expression or arrow | Reads happen after initialization |
| Recurse without depending on an outer variable name | Named function expression | The internal name doesn't leak outward |
| Capture outer `this` in a short callback | Arrow function | No dynamic receiver or `new` is required |
| Expose behavior on an object | Method definition | How detached uses retain the receiver |

An ordinary function can also ignore `this` completely. In that case, the main difference between a declaration and a traditional expression is binding and initialization. Read both the body and its call sites; the `function` keyword alone doesn't prove something is a method or constructor.

An arrow's expression body suits short transformations, but brevity isn't the central reason to choose one. Lexical `this`, no own `arguments`, and non-constructibility are the semantic differences. The related arrow-functions topic covers the complete rules and migration hazards.

## Testing function contracts

Start function tests from the public contract: given an input, what returns, what changes, and what throws. Proving only that "the function was called" misses absent returns, shifted arguments, and ignored callback results. Assert results for pure calculations and deliberate observable effects for command functions.

For optional input, separately test omission, explicit `undefined`, `null`, the wrong type, and valid falsy values. A destructured parameter also needs cases for a missing whole object, missing properties, and missing nested objects. These cases prove that defaults sit at the intended level.

Receiver tests should cover the intended method call and a detached call at minimum. If an API retains a callback, also test the full register, trigger, remove, trigger-again lifecycle and prove that removal uses the same function identity.

Test a higher-order function with a small double that records arguments and call counts. Assert which arguments the callback receives, in what order it runs, how its result is consumed, and whether exceptions propagate. For an async callback, also state whether the API awaits serially, awaits concurrently, or doesn't await at all.

Finally, test that function values are created at the lifetime your ownership model expects. Recreating functions in a loop or render path can break identity-based deduplication or removal; over-reusing one closure can share state that should be isolated. The contract to verify is lifetime, not a vague goal of fewer allocations.

## Function identity is part of the contract

Aliasing a variable doesn't create a function. After `const alias = handler`, `alias === handler` is `true`, and either name can be used interchangeably with an identity-based registry. A new function object appears only when a function expression, arrow, `bind`, or wrapper is evaluated again.

An API that stores callbacks by identity makes the caller manage that object's lifetime. Writing `event => handle(event)` inline at registration is convenient, but code must first retain the wrapper in a stable binding when the removal API also needs the function value. Repeating the same source can't recover the original object.

Caching a function object doesn't mean it belongs at global scope. A function may own request, component, or tenant state through a closure, and excessive reuse merges those ownership boundaries. Stable identity and correct isolation must be designed together.

| Operation | Preserves identity | Ownership effect |
| --- | --- | --- |
| `const alias = handler` | Yes | Both bindings reference one object |
| `handler.bind(receiver)` | No | A new object stores target and receiver |
| `(value) => handler(value)` | No | A new object captures the current lexical environment |
| Calling a factory for its inner function again | No | A new object usually owns fresh closure state |

<!-- /deep -->

[Checkpoint: javascript/functions](https://codewiki.com/javascript/functions/#checkpoint)

## Further reading

- [MDN: Functions reference](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions)
- [MDN: Default parameters](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Default_parameters)
- [MDN: Rest parameters](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/rest_parameters)
- [MDN: Arrow functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions)
- [ECMAScript specification: `OrdinaryCallBindThis`](https://tc39.es/ecma262/multipage/ecmascript-language-functions-and-classes.html#sec-ordinarycallbindthis)
