Skip to main content

Command Palette

Search for a command to run...

Function Declaration vs Expression

Master hoisting, timing, and when to use each in JavaScript

Updated
Function Declaration vs Expression
M

👋 Hey, I'm Mohd Kaif – a student documenting my journey through code. I write about what I'm learning in real-time – the wins, the struggles, and the "aha!" moments. From JavaScript and React to backend systems with Node.js, databases, DevOps, TypeScript, and AI integrations. This blog is my public learning journal: honest, evolving, and always exploring. If you're curious about any of these topics, let's learn and build together!

You know that moment when your code should work, but JavaScript throws an error?

I spent 90 minutes debugging a React component once. The logic was perfect. The syntax was clean. But every time I ran it, I got: TypeError: handleClick is not a function.

The culprit? I had written this:

<button onClick={handleClick}>Click me</button>

const handleClick = function() {
  console.log("Clicked!");
};

Instead of this:

function handleClick() {
  console.log("Clicked!");
}

<button onClick={handleClick}>Click me</button>

That's when I realized: the difference between function declarations and function expressions isn't just syntax—it's about timing, availability, and how JavaScript actually reads your code.


Does this sound familiar?

  • ❓ You've written functions both ways and they seem interchangeable

  • ❓ You've hit mysterious "not a function" errors that disappeared when you moved code around

  • ❓ You've seen both styles in tutorials but never understood when to use which

  • ❓ You've heard the term "hoisting" but it feels like JavaScript black magic

Here's the truth: If you've ever been confused by this, you're not failing—you're experiencing one of JavaScript's most subtle gotchas. Most tutorials gloss over it. This article won't.


✅ What You'll Learn

By the end of this post, you'll understand:

The real, practical difference between function declarations and expressions
Why hoisting matters (explained without the CS jargon)
When each type breaks your code—and how to avoid it
Which pattern to use for callbacks, event handlers, and conditional logic
How to debug hoisting-related errors in 30 seconds instead of 30 minutes

No prerequisites required. If you know what a function is, you're ready.


Why Functions Matter (The 30-Second Version)

Functions let you package logic into reusable chunks. Instead of copying code everywhere:

// Without functions (painful)
let total1 = 19.99 * 1.08; // Calculate with tax
let total2 = 49.99 * 1.08;
let total3 = 99.99 * 1.08;

You write it once:

// With a function (clean)
function addTax(price) {
  return price * 1.08;
}

let total1 = addTax(19.99);
let total2 = addTax(49.99);
let total3 = addTax(99.99);

Simple. Reusable. Maintainable.

Now here's where it gets interesting: there are two ways to define functions in JavaScript, and they behave differently under the hood.


Function Declarations: The "Always Available" Approach

A function declaration uses the function keyword and gives your function a name.

Syntax

function functionName(parameters) {
  // code here
}

Real Example

function calculateDiscount(price, percentage) {
  return price * (percentage / 100);
}

console.log(calculateDiscount(100, 20)); // 20

What Makes It Different

Here's the weird part: you can call a function declaration before you define it.

console.log(greet("Sarah")); // ✅ Works perfectly

function greet(name) {
  return `Hello, ${name}!`;
}

This works because JavaScript hoists the entire function to the top during the setup phase. More on that soon.


Function Expressions: The "Assigned When Reached" Approach

A function expression treats the function as a value and assigns it to a variable.

Syntax

const functionName = function(parameters) {
  // code here
};

Real Example

const calculateDiscount = function(price, percentage) {
  return price * (percentage / 100);
};

console.log(calculateDiscount(100, 20)); // 20

The Critical Difference

You cannot call a function expression before it's defined.

console.log(greet("Sarah")); // ❌ ReferenceError

const greet = function(name) {
  return `Hello, ${name}!`;
};

Why? Because the variable greet exists, but the function hasn't been assigned to it yet. JavaScript sees an uninitialized variable, not a function.


🛠️ Quick Exercise #1: Test the Difference

Open your browser console and try this:

Part A:

console.log(double(5));

function double(num) {
  return num * 2;
}

Part B:

console.log(triple(5));

const triple = function(num) {
  return num * 3;
};

What happens? Part A works. Part B throws an error.

Why? Function declarations are available immediately. Function expressions are not.


Hoisting: What's Actually Happening (Without the Jargon)

"Hoisting" sounds complicated. Here's the simple version:

Before JavaScript runs your code, it does a setup pass. During this pass:

  1. Function declarations → Fully stored in memory (name + body)

  2. Variables (let, const, var) → Reserved but not initialized

Think of it like this:

  • Function declaration = A tool you loaded into your toolbox before the job started

  • Function expression = A tool you assemble on-site as you need it

What JavaScript Sees Internally

When you write this:

console.log(add(2, 3));

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

JavaScript internally rearranges it to:

function add(a, b) {  // ← moved to top during setup
  return a + b;
}

console.log(add(2, 3)); // ← now it works

But when you write this:

console.log(add(2, 3));

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

JavaScript sees:

const add; // ← variable exists, but undefined

console.log(add(2, 3)); // ❌ can't call undefined

A Real-World Scenario That Broke My Code

Here's what happened in that React component I mentioned earlier:

function UserProfile() {
  return (
    <div>
      <button onClick={handleLogout}>Logout</button>
    </div>
  );
  
  const handleLogout = function() {
    console.log("Logging out...");
  };
}

Why it failed: When React rendered the component, handleLogout was referenced at the top but hadn't been assigned yet (function expression).

The fix: Either move it above the return, or use a declaration:

function UserProfile() {
  function handleLogout() {
    console.log("Logging out...");
  }
  
  return (
    <div>
      <button onClick={handleLogout}>Logout</button>
    </div>
  );
}

Now handleLogout is available everywhere in the function scope.


🛠️ Exercise #2: Debug the Broken Code

Here's a broken event handler. Can you spot the issue?

document.getElementById("submit").addEventListener("click", validateForm);

const validateForm = function() {
  console.log("Validating...");
};

Question: Will this work? If not, why?

Click to reveal the answer

No, it won't work. The addEventListener tries to use validateForm before it's assigned.

Fix option 1: Move the function expression above the listener:

const validateForm = function() {
  console.log("Validating...");
};

document.getElementById("submit").addEventListener("click", validateForm);

Fix option 2: Use a function declaration:

function validateForm() {
  console.log("Validating...");
}

document.getElementById("submit").addEventListener("click", validateForm);

Side-by-Side: Declaration vs Expression

Now that you understand how they behave, here's a comparison:

Feature Function Declaration Function Expression
Syntax function add() {} const add = function() {}
Naming Required Optional (can be anonymous)
Hoisting Entire function hoisted Only variable name hoisted
Can call before definition? ✅ Yes ❌ No
Use case Top-level utilities, predictable flow Callbacks, conditional logic, closures
Flexibility Less flexible More flexible

When Should You Use Each?

Use Function Declarations When:

✅ You want the function available everywhere in its scope
✅ You're writing standalone utility functions
✅ Order doesn't matter

Example: Helper functions in a module

function formatCurrency(amount) {
  return `$${amount.toFixed(2)}`;
}

function formatDate(date) {
  return date.toLocaleDateString();
}

Use Function Expressions When:

✅ You need to pass functions as arguments (callbacks)
✅ You're conditionally defining functions
✅ You want strict control over scope and timing

Example: Event handlers

const button = document.querySelector("#submit");

const handleClick = function(event) {
  event.preventDefault();
  console.log("Form submitted!");
};

button.addEventListener("click", handleClick);

Example: Conditional logic

const greet = user.isAdmin
  ? function() { return "Welcome, Admin!"; }
  : function() { return "Welcome, Guest!"; };

console.log(greet());

Example: Callbacks

const numbers = [1, 2, 3, 4];

const doubled = numbers.map(function(num) {
  return num * 2;
});

console.log(doubled); // [2, 4, 6, 8]

Common Mistakes (And How to Fix Them)

❌ Mistake 1: Calling a Function Expression Too Early

processPayment(100); // ❌ Error

const processPayment = function(amount) {
  console.log(`Processing $${amount}`);
};

Fix: Move the function above the call, or use a declaration.


❌ Mistake 2: Using Function Expressions in Conditional Blocks

if (user.isPremium) {
  function showFeature() {
    console.log("Premium feature");
  }
}

showFeature(); // ⚠️ May or may not work (browser-dependent)

Why it's risky: Function declarations inside blocks behave inconsistently across JavaScript engines.

Fix: Use a function expression with let or const:

let showFeature;

if (user.isPremium) {
  showFeature = function() {
    console.log("Premium feature");
  };
}

if (showFeature) {
  showFeature();
}

❌ Mistake 3: Forgetting That const Doesn't Enable Hoisting

const result = calculate(10, 5);

const calculate = function(a, b) {
  return a + b;
};

What you might think: "Since const prevents reassignment, maybe the function is available early?"

Reality: Nope. const only blocks reassignment. It doesn't hoist the function body.


🛠️ Exercise #3: Build a Real-World Scenario

Create a simple calculator with both function types:

// Your task:
// 1. Create a function DECLARATION called 'add'
// 2. Create a function EXPRESSION called 'subtract'
// 3. Try calling both BEFORE they're defined
// 4. Then call them AFTER they're defined
// 5. Observe which calls work and which don't

// Try calling here first:
console.log(add(10, 5));
console.log(subtract(10, 5));

// Define here:
// ... your code ...

// Try calling here again:
console.log(add(10, 5));
console.log(subtract(10, 5));

Expected outcome:

  • First add() call works

  • First subtract() call fails

  • Both second calls work


The Modern JavaScript Perspective

In modern codebases (especially with ES6+ and frameworks like React, Vue, Angular), you'll see:

More function expressions (and arrow functions), because:

  • They force you to define before use (more predictable)

  • They work better with const (prevents accidental reassignment)

  • They're required for modern patterns like closures and higher-order functions

Fewer function declarations, except for:

  • Top-level utility functions

  • Module-level helpers

  • Functions that genuinely need to be available everywhere


Quick Reference: Which One Should I Use?

Ask yourself:

  1. Do I need this function before it's defined?
    → Yes? Use a declaration.
    → No? Use an expression.

  2. Is this a callback or being passed as an argument?
    → Yes? Use an expression (or arrow function).

  3. Am I conditionally creating this function?
    → Yes? Use an expression.

  4. Is this a standalone utility I'll use everywhere?
    → Yes? Use a declaration.


What to Learn Next

Now that you understand function declarations and expressions, here's your learning path:

🎯 Next Steps:

  1. Arrow Functions → Learn the even shorter syntax and how this binding works differently

  2. Closures → Understand how functions remember their surrounding scope

  3. Higher-Order Functions → Use functions that accept or return other functions (map, filter, reduce)

  4. IIFE (Immediately Invoked Function Expressions) → Execute functions the moment they're defined

Why this order? Each concept builds on function expressions. Master this foundation first.


💬 Got Questions?

Drop a comment below! I'd love to hear about your experience with function declarations vs expressions—or help you debug a tricky scenario.

Here are topics for future articles:

  • Arrow Functions vs Regular Functions: Why this behaves differently and when it matters

  • Closures Explained Simply: How JavaScript "remembers" variables from outer scopes

  • Async/Await Without the Confusion: Write asynchronous code that actually makes sense

  • The Event Loop: What really happens when JavaScript runs your code


Key Takeaways

Let's wrap this up:

Function declarations are hoisted—you can call them before they appear in your code
Function expressions are not—you must define them first
Declarations are great for utilities and predictable, top-level functions
Expressions give you control, flexibility, and are essential for callbacks
Modern JavaScript favors expressions and arrow functions for most use cases

The bottom line: Both are valid. But knowing when to use each? That's what separates beginners from confident JavaScript developers.

Now you're not just writing functions—you're choosing the right type for the job.


Found this helpful? Share it with someone who's learning JavaScript. And if you're building something cool, drop a link in the comments—I'd love to see it!

Happy coding! 🚀

Zero to Full Stack Developer: From Basics to Production

Part 36 of 50

Complete full-stack web development series from zero to production. Learn HTML, CSS, JavaScript, TypeScript, React, Next.js, Node.js, databases, Docker, AWS, and AI integration. Build real-world projects step-by-step.

Up next

JavaScript Control Flow Explained

A hands-on guide to if, else, else if, and switch — with real-world examples, common mistakes, and exercises.