Function declaration vs function expression
A function declaration declares the function up front, so it is available anywhere in its scope, even above the line where it is written, while a function expression creates the function only when the assignment line runs. Both produce an ordinary function; the difference is when it appears in the scope and whether it must have a name.
Theory
TL;DR
function greet() {}is a declaration: it is hoisted together with its body, so you can call it anywhere in its scope.const greet = function () {}is an expression: only the variable is hoisted, the function appears after the assignment.- Calling an expression before the assignment throws
ReferenceErrorforconstandlet(temporal dead zone) orTypeError: greet is not a functionforvar. - A declaration is always named, an expression may be anonymous or named.
- Use declarations for the main functions of a module, and expressions or arrow functions for callbacks, handlers and parts of expressions.
Quick example
// --- Function Declaration ---
sayHi(); // works
function sayHi() {
console.log('Hi!');
}
// --- Function Expression ---
sayBye(); // error: the variable is not initialised yet
const sayBye = function () {
console.log('Bye!');
};Function declaration
A declaration starts a statement with the function keyword:
function sayHello() {
console.log('Hello!');
}Characteristics:
- Hoisting. The function is available above its declaration: the engine creates it, body included, while initialising the scope.
- The function name is fixed and available in the current scope.
- You can call it anywhere in the file or block, even above the declaration.
greet(); // works, although the function is declared below
function greet() {
console.log('Hello!');
}Result:
Hello!Function expression
A function expression is a function on the right-hand side of an expression, most often an assignment:
const sayHello = function () {
console.log('Hello!');
};or the arrow version:
const sayHello = () => console.log('Hello!');Characteristics:
- Not hoisted like a declaration. The variable is created, but the function inside it is unavailable until initialisation.
- It can be anonymous (no name) or named (
const f = function inner() {}, whereinneris visible only inside the function itself and shows up nicely in stack traces). - It is an expression, so it can be called only after the assignment.
sayHello(); // error: the function does not exist yet
const sayHello = function () {
console.log('Hello!');
};Key differences
| Property | Function Declaration | Function Expression |
|---|---|---|
| Hoisting | Yes, available before the declaration | No, unavailable before the assignment |
| When it is created | When the scope is initialised | While the line is executed |
| Can it be called before the declaration? | Yes | No |
| Function name | Required, a declaration is always named | May be anonymous or named |
| Good for | Core functions needed everywhere | Local ones, passed as arguments, callbacks |
| Usage example | function sum(a, b) {} | const sum = (a, b) => {} |
What to choose
- Function declaration when the function is used widely across the module and it is convenient to call it anywhere, for example top-level helpers.
- Function expression or arrow function when the function is local: an event handler, a callback, an argument to
map/filter, part of an expression, or a value returned from another function.
Short version to remember:
function declarationis created up front (hoisting).function expressionis created at the moment of execution.
Common mistakes
- Assuming expressions are hoisted too. Only the variable declaration is hoisted. With
varit holdsundefined, so the call throwsTypeError: sayBye is not a function; withconstandletthe variable is in the temporal dead zone, so the call throwsReferenceError: Cannot access 'sayBye' before initialization. - Relying on declaration hoisting inside a block. In strict mode a function declared inside an
ifor a loop is scoped to that block and does not exist outside it. - Making callbacks anonymous for no reason. A named expression (
const handler = function handler() {}) gives a meaningful stack trace while debugging. - Confusing arrow functions with
functionexpressions. An arrow function is also an expression, but it has no ownthisorargumentsand cannot be used as a constructor. - Declaring the same declaration twice. The later declaration silently overrides the earlier one, and the bug only shows up at runtime.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.