Skip to main content

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 ReferenceError for const and let (temporal dead zone) or TypeError: greet is not a function for var.
  • 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

javascript
// --- 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:

javascript
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.
javascript
greet(); // works, although the function is declared below function greet() { console.log('Hello!'); }

Result:

javascript
Hello!

Function expression

A function expression is a function on the right-hand side of an expression, most often an assignment:

javascript
const sayHello = function () { console.log('Hello!'); };

or the arrow version:

javascript
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() {}, where inner is 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.
javascript
sayHello(); // error: the function does not exist yet const sayHello = function () { console.log('Hello!'); };

Key differences

PropertyFunction DeclarationFunction Expression
HoistingYes, available before the declarationNo, unavailable before the assignment
When it is createdWhen the scope is initialisedWhile the line is executed
Can it be called before the declaration?YesNo
Function nameRequired, a declaration is always namedMay be anonymous or named
Good forCore functions needed everywhereLocal ones, passed as arguments, callbacks
Usage examplefunction 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 declaration is created up front (hoisting). function expression is created at the moment of execution.

Common mistakes

  • Assuming expressions are hoisted too. Only the variable declaration is hoisted. With var it holds undefined, so the call throws TypeError: sayBye is not a function; with const and let the variable is in the temporal dead zone, so the call throws ReferenceError: Cannot access 'sayBye' before initialization.
  • Relying on declaration hoisting inside a block. In strict mode a function declared inside an if or 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 function expressions. An arrow function is also an expression, but it has no own this or arguments and 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 ready
Premium

A concise answer to help you respond confidently on this topic during an interview.