Skip to content
JavaScript js functions 4 min read

Function Expressions

A function expression defines a function as part of a larger expression — most often by assigning it to a variable. Unlike a function declaration, which stands on its own as a statement, a function expression produces a value: a function object you can store, pass around, or invoke later. This subtle difference changes how the function is hoisted and unlocks patterns like callbacks, conditional definitions, and functions returned from other functions. Understanding expressions is the foundation for almost every advanced functional technique in JavaScript.

Syntax

The classic form uses the function keyword on the right-hand side of an assignment. Because the function is a value, the statement ends with a semicolon like any other assignment.

const greet = function (name) {
  return `Hello, ${name}!`;
};

console.log(greet("Ada"));

Output:

Hello, Ada!

When the function keyword has no name after it, this is an anonymous function expression. The variable greet is just a reference to it — the function itself has no identifier of its own beyond what JavaScript infers.

Anonymous vs named expressions

You may also give a function expression a name. This is a named function expression, and the name is scoped only to the function’s own body — it is not visible outside.

const factorial = function fac(n) {
  return n <= 1 ? 1 : n * fac(n - 1);
};

console.log(factorial(5));
console.log(typeof fac); // not defined in outer scope

Output:

120
undefined

The internal name (fac) is useful for two reasons: it lets the function call itself reliably even if the outer variable is reassigned, and it produces clearer stack traces when an error is thrown. With purely anonymous expressions, modern engines often infer a name from the variable, but only at definition time.

AspectAnonymous expressionNamed expression
Syntaxconst f = function () {}const f = function inner() {}
Self-referenceVia the outer variable onlyVia the internal name (reliable)
Stack tracesInferred name (may be blank)Uses the explicit internal name
Internal name in scopeNoYes — inside the body only

Hoisting: the key difference

This is where expressions and declarations diverge sharply. A function declaration is fully hoisted — both its name and body move to the top of the scope, so you can call it before it appears in the source. A function expression is not. Only the variable binding is hoisted; the assignment runs in place. Calling it early throws.

declared(); // works — declaration is hoisted

function declared() {
  return "I'm hoisted";
}

expressed(); // ReferenceError or TypeError

const expressed = function () {
  return "I'm not";
};

Output:

ReferenceError: Cannot access 'expressed' before initialization

With const and let, the variable sits in the temporal dead zone until its declaration is evaluated, so you get a ReferenceError. With var, the binding is hoisted as undefined, so calling it produces a TypeError: expressed is not a function instead. Either way, the function body is unavailable until the assignment executes.

Tip: Treat “not hoisted as callable” as a feature, not a limitation. It forces you to define a function before you use it, which keeps code reading top-to-bottom and avoids surprising forward references.

When to use function expressions

Expressions are the natural choice whenever a function is being used as a value rather than declared as a reusable named subroutine.

Callbacks

Passing a function to another function — map, addEventListener, setTimeout, promise handlers — is the most common case. The function exists only to be handed off.

const numbers = [1, 2, 3, 4];
const doubled = numbers.map(function (n) {
  return n * 2;
});

console.log(doubled);

Output:

[ 2, 4, 6, 8 ]

In modern code, an arrow function is usually preferred for short callbacks like this, but arrows are themselves a form of function expression — the choice is about syntax and this binding, not about the expression-vs-declaration distinction.

Conditional definition

Because an expression runs where it sits, you can choose which function to assign based on runtime conditions. A declaration cannot do this cleanly, since declarations inside blocks behave inconsistently across environments.

const isProduction = process.env.NODE_ENV === "production";

const log = isProduction
  ? function () {} // no-op in production
  : function (...args) {
      console.log("[debug]", ...args);
    };

log("starting up");

Assigning to object properties

Storing a function on an object literal is itself a function expression (the method shorthand is sugar over the same idea).

const calculator = {
  add: function (a, b) {
    return a + b;
  },
  // method shorthand — equivalent and cleaner
  subtract(a, b) {
    return a - b;
  },
};

console.log(calculator.add(2, 3), calculator.subtract(5, 1));

Output:

5 4

Best Practices

  • Reach for a function expression when the function is a value being passed, returned, or conditionally chosen; use a declaration for top-level named utilities.
  • Prefer named function expressions for recursion and for clearer stack traces during debugging.
  • Define expressions before you call them — they are not hoisted as callable, so order matters.
  • Use const (not var) for function expressions so reassignment is caught and the temporal dead zone gives a clear error.
  • For short inline callbacks, prefer arrow functions; switch to a full function expression only when you need its own this or an internal name.
  • Keep anonymous expressions short; if a function grows complex, give it a name to aid readability and tooling.
Last updated June 1, 2026
Was this helpful?