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.
| Aspect | Anonymous expression | Named expression |
|---|---|---|
| Syntax | const f = function () {} | const f = function inner() {} |
| Self-reference | Via the outer variable only | Via the internal name (reliable) |
| Stack traces | Inferred name (may be blank) | Uses the explicit internal name |
| Internal name in scope | No | Yes — 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(notvar) 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
functionexpression only when you need its ownthisor an internal name. - Keep anonymous expressions short; if a function grows complex, give it a name to aid readability and tooling.