A function expression in JavaScript is a way of defining a function and assigning it to a variable, property, or passing it as an argument, using the function keyword as part of an expression rather than a standalone statement. Unlike function declarations, function expressions are not hoisted with their definition, so the function can only be used after it has been assigned.
JavaScript Function Expression
Think of a function expression like writing a recipe on a piece of paper and then putting that paper inside a labeled box (a variable). You can't use the recipe until you've actually placed it in the box. Only after the paper is inside the box can you take it out and use it. Similarly, a function expression only becomes usable after the line of code where it is assigned to a variable has run.
Consider a ride-sharing app like Uber or Ola that calculates fare estimates differently depending on the time of day — normal fare, peak-hour fare, or night-charge fare. Developers can store different fare calculation logic as function expressions assigned to variables like calculateNormalFare, calculatePeakFare, and calculateNightFare. Based on the current time, the app dynamically selects and invokes the correct function expression to calculate the estimated fare shown to the user before they confirm the ride. Since these are function expressions rather than declarations, they are cleanly scoped to where they are defined and don't clutter the global namespace.
Function expressions are important because they allow functions to be treated as values — they can be assigned to variables, stored in objects or arrays, passed as arguments to other functions, and returned from functions. This flexibility enables patterns like callbacks, higher-order functions, and module-like encapsulation using IIFEs. Since function expressions are not hoisted with their definitions, they also help developers write more predictable code by ensuring functions are defined before they are used.
- Anonymous Function Expression: A function expression without a name, commonly assigned directly to a variable or passed as an argument, such as const greet = function() { return 'Hello'; };
- Named Function Expression: A function expression that has a name, which is useful for recursion and debugging since the function name appears in stack traces, such as const factorial = function fact(n) { return n <= 1 ? 1 : n * fact(n - 1); };
- Immediately Invoked Function Expression (IIFE): A function expression that is defined and executed immediately, often used to create a private scope, such as (function() { console.log('Executed immediately'); })();
- Function Expression as Object Property: A function expression assigned as a value to an object's property, effectively creating a method, such as const user = { greet: function() { return 'Hi there'; } };
- Function Expression Passed as Argument: A function expression passed directly into another function as an argument, commonly used in array methods and event handlers, such as array.forEach(function(item) { console.log(item); });
- Trying to Call a Function Expression Before Its Definition: Since function expressions are not hoisted with their definitions, calling the variable before the line where the function is assigned results in a ReferenceError (for let/const) or undefined is not a function error (for var). For example, calling greet() before const greet = function() {...} throws an error, unlike function declarations which are fully hoisted.
- Confusing Named Function Expressions with Declarations: Developers sometimes assume that a named function expression like const fn = function myFunc() {} makes myFunc available in the outer scope, but myFunc is only accessible within the function's own body, not outside it. Attempting to call myFunc() from outside the expression results in a ReferenceError.
- Forgetting Parentheses in IIFE Syntax: A common syntax mistake when writing an IIFE is forgetting to wrap the function expression in parentheses before the invoking parentheses, such as writing function() {}() instead of (function() {})(), which results in a SyntaxError because JavaScript interprets it as an invalid function declaration followed by an unexpected token.
- Losing Function Reference on Reassignment: Since function expressions assigned with let or var can be reassigned, accidentally reassigning the variable elsewhere in the code (e.g., myFunction = null) removes access to the original function, causing calls to myFunction() later to throw a TypeError, especially problematic in larger codebases with shared variable names.
- Overusing IIFEs in Modern Code with ES Modules: Developers coming from older JavaScript codebases sometimes wrap every file in an IIFE to avoid global scope pollution, even when using modern ES modules (import/export), which already provide module-level scoping. This adds unnecessary complexity since ES modules make IIFEs redundant for scope isolation in most modern projects.
- Use Named Function Expressions for Recursive Logic: When writing a function that needs to call itself, use a named function expression instead of relying on the outer variable name, since the outer variable could be reassigned elsewhere, breaking the recursion. The internal name remains stable and reliable within the function's own scope.
- Prefer const Over var When Assigning Function Expressions: Always assign function expressions using const instead of var or let, unless reassignment is genuinely needed. This prevents accidental overwriting of the function reference and makes the code's intent clearer to other developers reading it.
- Use IIFEs Only When Scope Isolation Is Genuinely Needed: Reserve IIFEs for legacy script-based environments without module support, or for specific patterns like the module pattern or avoiding global variable leaks in third-party embeddable scripts. In modern projects using ES modules or bundlers like Webpack and Vite, prefer regular module scoping instead.
- Keep Function Expressions Readable When Used Inline: When passing function expressions as callback arguments (e.g., to map, filter, or event listeners), keep the logic short and readable. For longer or reusable logic, define the function expression separately with a descriptive name and pass the reference instead of writing a large inline function.
- Leverage Function Expressions for Conditional Logic Assignment: Use function expressions when you need to assign different implementations of a function based on a condition, such as choosing between two calculation strategies at runtime, since function declarations cannot be conditionally reassigned as cleanly due to hoisting behavior and block-scoping quirks.
Function expressions are a flexible way to define functions in JavaScript by assigning them to variables, object properties, or passing them directly as arguments. Unlike function declarations, they are not fully hoisted, meaning they must be defined before use. Function expressions can be anonymous, named, or immediately invoked (IIFE), and they play a crucial role in patterns like callbacks, closures, and module encapsulation. Understanding the distinction between function expressions and declarations, along with their scoping and hoisting behavior, is essential for writing predictable and maintainable JavaScript code.