JavaScript Function Expressions vs Declarations
Understand the differences between JavaScript function expressions and declarations. Covers hoisting, syntax, named vs anonymous expressions, use cases, performance, and when to choose each approach in your code.
JavaScript has two primary ways to create functions: declarations and expressions. They look similar but behave differently. Function declarations are hoisted and available throughout their scope. Function expressions are not hoisted and only available after the line where they are defined. This difference affects how you structure your code, when functions become callable, and which patterns are possible.
This tutorial breaks down every difference between function declarations and expressions with clear examples, a complete comparison table, and practical guidance on when to use each.
Side-by-Side Syntax
// Function Declaration
function add(a, b) {
return a + b;
}
// Function Expression
const add = function (a, b) {
return a + b;
};Both create a function that adds two numbers. The syntax difference is small, but the behavior difference matters.
| Feature | Declaration | Expression |
|---|---|---|
| Starts with | function keyword | Variable keyword (const, let, var) |
| Function name | Required | Optional (anonymous or named) |
| Semicolon | Not needed after } | Required (it is an assignment statement) |
| Result | Creates a named function in current scope | Assigns a function value to a variable |
The Hoisting Difference
This is the most important distinction. Function declarations are hoisted. Function expressions are not.
Declarations: Available Everywhere in Their Scope
// This works perfectly
console.log(square(5)); // 25
function square(n) {
return n * n;
}JavaScript processes all function declarations before executing any code. The function exists from the very beginning of its scope.
Expressions: Only Available After Definition
// This throws an error
// console.log(square(5)); // ReferenceError: Cannot access 'square' before initialization
const square = function (n) {
return n * n;
};
console.log(square(5)); // 25The const variable square exists in the scope (it occupies the slot due to the Temporal Dead Zone), but accessing it before the assignment line throws a ReferenceError.
var Expressions: Hoisted but Undefined
// console.log(square(5)); // TypeError: square is not a function
var square = function (n) {
return n * n;
};
console.log(square(5)); // 25With var, the variable itself is hoisted and initialized to undefined. Calling undefined() throws a TypeError, not a ReferenceError. This is one more reason to avoid var.
Hoisting Summary
Declaration: fully hoisted (function is callable). const/let expression: in Temporal Dead Zone (ReferenceError). var expression: hoisted as undefined (TypeError if called).
Anonymous vs Named Expressions
Anonymous Function Expression
const greet = function (name) {
return `Hello, ${name}!`;
};The function has no name. In stack traces, it shows as greet (the variable name) in modern engines, but older tools may show "anonymous".
Named Function Expression
const greet = function sayHello(name) {
return `Hello, ${name}!`;
};
console.log(greet("Alice")); // "Hello, Alice!"
// console.log(sayHello("Alice")); // ReferenceError: sayHello is not definedThe name sayHello is only accessible inside the function body. This is useful for:
- Better stack traces: the function name appears in error messages
- Recursion: the function can call itself by name
const countdown = function count(n) {
console.log(n);
if (n > 0) {
count(n - 1); // recursive call using internal name
}
};
countdown(3); // 3, 2, 1, 0Overwriting and Reassignment
Declarations Can Be Redeclared
function greet() {
return "Hello!";
}
function greet() {
return "Hi!"; // silently overwrites the first
}
console.log(greet()); // "Hi!"This happens without any warning, which can cause subtle bugs.
const Expressions Prevent Reassignment
const greet = function () {
return "Hello!";
};
// greet = function () {
// return "Hi!";
// }; // TypeError: Assignment to constant variable
console.log(greet()); // "Hello!"Using const makes it impossible to accidentally replace the function. This predictability is a significant advantage.
let Expressions Allow Reassignment
let greet = function () {
return "Hello!";
};
greet = function () {
return "Hi!"; // reassignment is allowed
};
console.log(greet()); // "Hi!"Use let for function expressions only if you genuinely need to reassign the function later (which is rare).
Conditional Function Creation
Function expressions can be assigned conditionally. Declarations cannot (or behave inconsistently):
// Expression: works reliably
let validator;
if (strictMode) {
validator = function (input) {
return input.length > 10 && /^[a-zA-Z]+$/.test(input);
};
} else {
validator = function (input) {
return input.length > 0;
};
}// Declaration: problematic in strict mode
// Different browsers handle this differently
if (strictMode) {
function validate(input) { /* strict rules */ }
} else {
function validate(input) { /* loose rules */ }
}
// Don't do this -- behavior varies across environmentsNever Declare Functions Inside if Blocks
Putting function declarations inside if, for, or other blocks leads to inconsistent behavior across browsers and JavaScript versions. Use function expressions assigned to variables instead.
Expressions as Callbacks
Function expressions (including anonymous ones) are the natural choice for callbacks:
// Anonymous expression as callback
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(function (n) {
return n * 2;
});
console.log(doubled); // [2, 4, 6, 8, 10]// Named expression as callback (better stack traces)
const filtered = numbers.filter(function isEven(n) {
return n % 2 === 0;
});
console.log(filtered); // [2, 4]// Declaration as callback (valid but uncommon)
const sorted = numbers.sort(compare);
function compare(a, b) {
return a - b;
}Object Methods
Both styles work as object methods:
const calculator = {
// Method shorthand (most common in modern JS)
add(a, b) {
return a + b;
},
// Expression as property
subtract: function (a, b) {
return a - b;
},
};
console.log(calculator.add(5, 3)); // 8
console.log(calculator.subtract(5, 3)); // 2The method shorthand (add(a, b)) is syntactic sugar for a function expression assigned to a property. It is the preferred style for object methods.
Practical Comparison: Building a Module
Using Declarations
function createUser(name, email) {
return { name, email, createdAt: new Date() };
}
function validateUser(user) {
const errors = [];
if (!user.name) errors.push("Name required");
if (!user.email) errors.push("Email required");
return errors;
}
function formatUser(user) {
return `${user.name} <${user.email}>`;
}
// All functions available anywhere in the file
const user = createUser("Alice", "alice@test.com");
const errors = validateUser(user);
console.log(formatUser(user));Using Expressions
const createUser = function (name, email) {
return { name, email, createdAt: new Date() };
};
const validateUser = function (user) {
const errors = [];
if (!user.name) errors.push("Name required");
if (!user.email) errors.push("Email required");
return errors;
};
const formatUser = function (user) {
return `${user.name} <${user.email}>`;
};
// Functions only available after their definition
const user = createUser("Alice", "alice@test.com");
const errors = validateUser(user);
console.log(formatUser(user));Both approaches work. Declarations give you flexibility in file organization (order does not matter). Expressions give you predictability (code runs top-to-bottom) and const protection.
Decision Table: When to Use Each
| Scenario | Use declaration | Use expression |
|---|---|---|
| Top-level utility function | Yes | Also fine |
| Function needs to be called before definition | Yes | No (not possible) |
| Callback passed to another function | Possible but uncommon | Yes (natural fit) |
| Conditional function assignment | No (unreliable) | Yes |
| Prevent accidental overwriting | No (can be redeclared) | Yes (use const) |
| Recursive function needing a name | Yes | Yes (named expression) |
| Object method | Use method shorthand | Use method shorthand |
| Immediately invoked (IIFE) | Possible but awkward | Yes (natural fit) |
General Recommendation
Use function declarations for standalone, top-level functions where hoisting is helpful. Use const function expressions for callbacks, conditional assignments, and when you want the safety of const. Many modern codebases use expressions exclusively for consistency, and that approach works well too.
Rune AI
Key Insights
- Declarations are fully hoisted: callable before their definition line in the source code
- Expressions follow variable rules:
constblocks access until defined, prevents reassignment - Named expressions help debugging: the name appears in stack traces and enables recursion
- Never declare functions inside if/for blocks: use expressions for conditional function creation
- Choose one style and be consistent: declarations or expressions both work, consistency matters most
Frequently Asked Questions
Is there a performance difference between declarations and expressions?
Can I export both declarations and expressions from a module?
Why do some style guides prefer const expressions?
Should I always name my function expressions?
Do arrow functions replace function expressions?
Conclusion
Function declarations and expressions both create functions, but they differ in hoisting, naming, and reassignment behavior. Declarations are hoisted and callable anywhere in their scope. Expressions follow variable rules: const expressions are not hoisted and cannot be reassigned, giving you predictability and safety. Choose declarations for top-level utility functions where order flexibility helps. Choose const expressions for callbacks, conditional assignments, and codebases that prefer top-to-bottom readability. In practice, either approach produces correct, maintainable code when used consistently.
More in this topic
OffscreenCanvas API in JS for UI Performance
Master the OffscreenCanvas API to offload rendering from the main thread. Covers worker-based 2D and WebGL rendering, animation loops inside workers, bitmap transfer, double buffering, chart rendering pipelines, image processing, and performance measurement strategies.
Advanced Web Workers for High Performance JS
Master Web Workers for truly parallel JavaScript execution. Covers dedicated and shared workers, structured cloning, transferable objects, SharedArrayBuffer with Atomics, worker pools, task scheduling, Comlink RPC patterns, module workers, and performance profiling strategies.
JavaScript Macros and Abstract Code Generation
Master JavaScript code generation techniques for compile-time and runtime metaprogramming. Covers AST manipulation, Babel plugin authorship, tagged template literals as macros, code generation pipelines, source-to-source transformation, compile-time evaluation, and safe eval alternatives.