JavaScript Function Expressions

In JavaScript, functions can be defined in two main ways: function declarations and function expressions. A function expression assigns a function to a variable. Understanding the differences between them — especially regarding hoisting — is crucial for writing predictable code.

Function Declaration vs Expression

A function declaration uses the 'function' keyword followed by a name. A function expression assigns an (often anonymous) function to a variable. The key difference is hoisting: declarations are hoisted, expressions are not.

Declaration vs Expression
// Function Declaration — hoisted, can be called before definition
console.log(add(2, 3));

function add(a, b) {
  return a + b;
}

// Function Expression — NOT hoisted
// console.log(subtract(5, 2)); // Would throw ReferenceError

const subtract = function(a, b) {
  return a - b;
};

console.log(subtract(5, 2));

Anonymous Function Expressions

Anonymous functions have no name after the 'function' keyword. They are commonly used as function expressions, callbacks, and event handlers. The variable name serves as the identifier.

Anonymous Functions
// Anonymous function expression
const greet = function(name) {
  return `Hello, ${name}!`;
};
console.log(greet("Alice"));

// Anonymous function as a callback
let numbers = [3, 1, 4, 1, 5];
let sorted = numbers.sort(function(a, b) {
  return a - b;
});
console.log(sorted);

// Anonymous function in setTimeout
setTimeout(function() {
  console.log("Delayed message");
}, 0);

// Arrow function (also anonymous)
const double = (n) => n * 2;
console.log(double(5));

Named Function Expressions

A named function expression has a name, but that name is only accessible inside the function itself. This is useful for recursion and for better stack traces during debugging.

Named Function Expressions
// Named function expression
const factorial = function fact(n) {
  if (n <= 1) return 1;
  return n * fact(n - 1);  // 'fact' is accessible inside
};

console.log(factorial(5));
console.log(factorial(10));

// The name 'fact' is NOT accessible outside
// console.log(fact(5)); // Would throw ReferenceError

// Named expressions have better debugging
const handler = function onClickHandler() {
  return "clicked";
};
console.log(handler.name);

// Anonymous expression uses variable name
const myFunc = function() {};
console.log(myFunc.name);

Hoisting Differences

Function declarations are fully hoisted — they can be called before they appear in code. Function expressions (with var, let, or const) are NOT hoisted — the variable exists but the function is not assigned yet.

Hoisting Behavior
// Declarations are hoisted completely
console.log(declaredFunc());

function declaredFunc() {
  return "I'm hoisted!";
}

// var expression: variable hoisted as undefined
try {
  console.log(varFunc());
} catch (e) {
  console.log("var error:", e.message);
}
var varFunc = function() { return "var func"; };

// let/const expression: in temporal dead zone
try {
  console.log(constFunc());
} catch (e) {
  console.log("const error:", e.message);
}
const constFunc = function() { return "const func"; };

// After assignment, everything works
console.log(varFunc());
console.log(constFunc());

IIFE (Immediately Invoked Function Expression)

An IIFE is a function expression that is defined and immediately called. It creates a private scope, preventing variables from polluting the global namespace. This was essential before ES6 modules and let/const.

IIFE Pattern
// Classic IIFE
(function() {
  let secret = "hidden";
  console.log("IIFE executed, secret:", secret);
})();

// 'secret' is not accessible here
// console.log(secret); // ReferenceError

// IIFE with return value
const module = (function() {
  let count = 0;
  return {
    increment() { return ++count; },
    getCount() { return count; }
  };
})();

console.log(module.increment());
console.log(module.increment());
console.log(module.getCount());

// Arrow IIFE
const result = (() => {
  return 42;
})();
console.log(result);
FeatureDeclarationExpression
Syntaxfunction name() {}const name = function() {}
HoistingFully hoistedNot hoisted
Name requiredYesNo (anonymous OK)
Can be IIFENoYes
this bindingDynamicDynamic (arrow: lexical)
Use in blocksInconsistentReliable with let/const
📝 Note: Use function declarations for top-level functions that should be available throughout the module. Use function expressions (especially const) when you want to prevent hoisting, assign functions conditionally, or pass them as callbacks. Many style guides prefer const with arrow functions for consistency.
Exercise:
What happens if you call a function expression before its definition line?
Try it YourselfCtrl+Enter to run
Click Run to see the output here.