JavaScript: Functions

Master every way to define and use functions: declarations, expressions, arrows, closures, callbacks, and higher-order patterns.

Function Declaration

A function declaration defines a named function using the function keyword at the statement level. It is fully hoisted, meaning it can be called before the line where it is written.

Function Declaration

Named, hoisted, callable anywhere in the same scope: the classic way to define a function.

  • Syntax: function name(params) { body }
  • Fully hoisted: callable above the definition in the same scope
  • Always has a name: unnamed declarations are a SyntaxError
  • Best for utility functions and named callbacks

Function Declaration

add(3, 4) works above the function definition: declarations are fully hoisted.

Function Expression

A function expression assigns a function to a variable. Unlike declarations, function expressions are not hoisted, the variable exists but holds undefined until the assignment line is reached.

Function Expression

Function stored in a variable: not hoisted, only usable after the assignment line.

  • Syntax: const fn = function(params) { body };
  • Not hoisted: calling before the line throws a TypeError
  • Can be named (named function expression) for better stack traces
  • Useful when the function is conditional or passed as an argument

Function Expression

Naming the function expression (fact) allows it to reference itself recursively.

Anonymous Functions

An anonymous function has no name. They are most commonly used as inline callbacks passed directly to another function. Since ES6, arrow functions are the preferred syntax for anonymous functions.

Anonymous Functions

Functions without a name: used inline as callbacks or immediately invoked.

  • No name between function and (
  • Cannot reference themselves (no self-recursion without a name)
  • Show as anonymous in stack traces: harder to debug
  • Arrow functions are syntactically shorter for anonymous use

Anonymous Functions

Anonymous functions work well inline: arrow functions make them even shorter.

Arrow Functions (=>)

Arrow functions are a concise ES6 syntax for function expressions. Their most important behavioural difference is that they do not have their own this, they inherit it from the surrounding scope.

Arrow Functions

Shorter syntax + lexical this: the default choice for callbacks and expressions.

  • Single expression: const double = n => n * 2;: implicit return
  • Multiple params: (a, b) => a + b
  • Body block: (a, b) => { const sum = a + b; return sum; }
  • No own this: borrows from the enclosing scope (great for methods called as callbacks)
  • Cannot be used as constructors (new throws TypeError)

Arrow Function Syntax

Single-expression arrows return implicitly: add braces and return for multi-line bodies.

Function Parameters and Arguments

Parameters are the named placeholders defined in the function signature. Arguments are the actual values passed when calling the function. JavaScript does not enforce the number of arguments, missing ones are undefined and extras are silently ignored (unless captured with rest).

Parameters vs Arguments

Parameters are the definition; arguments are the values: JavaScript does not enforce the count.

  • Too few arguments → missing params are undefined
  • Too many arguments → extras are silently ignored (or caught with ...rest)
  • Arguments are passed by value for primitives, by reference for objects
  • Use default parameters or guard with param ?? default to handle missing args

Parameters and Arguments

Objects are passed by reference: mutating inside the function changes the original.

Default Parameters

Default parameters assign a fallback value to a parameter when the argument is undefined (not passed, or explicitly passed as undefined). They replace the old param = param || default guard pattern.

Default Parameters

Assign fallback values in the signature: cleaner than manual || guards inside the body.

  • Syntax: function fn(a, b = 10) { }
  • Triggered only when the argument is undefined: not for null or 0
  • Default can be any expression, including a function call
  • Parameters with defaults should come after required parameters

Default Parameters

null does not trigger the default: only undefined (missing argument) does.

Rest Parameters (...)

The rest parameter syntax collects all remaining arguments into a real array. It replaces the legacy arguments object and works properly with arrow functions and array methods.

Rest Parameters

Collects remaining arguments into an array: replaces the old arguments object.

  • Syntax: function fn(a, b, ...rest) { }
  • Must be the last parameter: only one rest param allowed
  • Is a real array: supports map, filter, reduce
  • The old arguments object is array-like but not a real array, and does not work in arrow functions

Rest Parameters

rest is a real array: unlike arguments, it has map, filter, and join available directly.

Return Statement

The return statement exits a function and optionally sends a value back to the caller. A function without a return statement always returns undefined.

return Statement

Exits the function and optionally sends a value back: no return means undefined.

  • A function stops executing the moment return is reached
  • return; with no value returns undefined
  • Multiple return statements are fine: use early returns for guard clauses
  • ASI pitfall: never put the return value on a new line after return

Return Statement

Early return for guard clauses keeps the happy path unindented: a clean, readable pattern.

Function Scope

Variables declared inside a function are scoped to that function: they do not exist outside it. Functions also form a closure over their outer scope, giving them access to variables from the containing environment.

Function Scope

Variables inside a function are private to it: outer variables are readable but not the reverse.

  • Inner variables are not visible outside the function
  • The function can read outer (enclosing) variables: this is the closure mechanism
  • Each function call gets its own independent set of local variables
  • Nested functions form closures: they remember the outer scope even after it returns

Function Scope and Closures

inner() can see everything above it: but outer() cannot see inside inner().

Immediately Invoked Function Expressions (IIFE)

An IIFE is a function that is defined and called in the same expression. It creates a private scope, preventing variables from leaking into the surrounding context: a pattern that predates ES6 modules.

IIFE

Define and call in one shot: creates an isolated scope instantly.

  • Syntax: (function() { ... })() or (() => { ... })()
  • Variables inside the IIFE do not pollute the outer scope
  • Still useful for initialisation code that should run once and stay encapsulated
  • ES6 modules provide block scoping naturally: IIFEs are less necessary today

IIFE

The result can be captured: the internal variables remain completely private.

Callback Functions

A callback is a function passed as an argument to another function, to be called later. Callbacks are the foundation of asynchronous JavaScript and built-in array methods like forEach, map, and filter.

Callback Functions

A function you hand to another function to call at the right moment.

  • The receiving function decides when and how to invoke the callback
  • Can be named or anonymous (arrow function)
  • Array methods: map, filter, reduce, forEach all take callbacks
  • Async callbacks: setTimeout, event listeners, fetch, run after an operation completes

Callback Functions

repeat() does not know what the callback does: it just calls it at the right time.

Higher-Order Functions

A higher-order function is one that takes a function as an argument, returns a function, or both. They are the foundation of functional programming patterns in JavaScript.

Higher-Order Functions

Functions that operate on other functions: the basis of map, filter, reduce, and closures.

  • Takes a function as argument: arr.map(fn), arr.filter(fn)
  • Returns a function: enables partial application and currying
  • Built-in HOFs: map, filter, reduce, sort, forEach
  • Returning a function creates a closure: the inner function remembers the outer variables

Higher-Order Functions

multiplier returns a closure: double and triple each remember their own factor.

Pure Functions vs Side Effects

A pure function always returns the same output for the same input and causes no side effects. Impure functions interact with the outside world: logging, mutating state, making network calls. Both have their place, but pure functions are easier to test and reason about.

Pure vs Impure

Pure functions are predictable and testable: prefer them for data transformation logic.

  • Pure: same input → same output, no external changes
  • Side effects: modifying outer variables, DOM updates, console.log, HTTP requests, writing files
  • Pure functions are easy to test: call with inputs, check output, no setup needed
  • Real programs need side effects: keep them at the edges, pure logic in the middle

Pure vs Impure Functions

addToTotal depends on external state: the same call can return different results.

Function Hoisting

Function declarations are fully hoisted to the top of their scope: the entire definition, not just the name. Function expressions assigned to const or let are in the TDZ until their line is reached.

Function Hoisting

Declarations are fully hoisted: expressions are not. Call order matters for expressions.

  • Function declaration: fully hoisted, callable anywhere in the scope
  • Function expression (const fn = function() {}): TDZ until the line, calling before throws TypeError
  • Arrow function expression: same as function expression: not hoisted
  • Best practice: define before use regardless: hoisting is implementation detail, not a style guide

Function Hoisting

Declaration works above its definition: expression throws TypeError if called too early.

Knowledge Check

1. What is the key difference between a function declaration and a function expression?

2. What does an arrow function NOT have compared to a regular function?

3. What happens when a function is called with fewer arguments than parameters?

4. What does a function return when there is no return statement?

5. What is an IIFE?

6. What is a higher-order function?

7. What defines a pure function?

8. What does the rest parameter (...args) do?