Function Declaration vs Expression
Master hoisting, timing, and when to use each in JavaScript

👋 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:
Function declarations → Fully stored in memory (name + body)
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 worksFirst
subtract()call failsBoth 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:
Do I need this function before it's defined?
→ Yes? Use a declaration.
→ No? Use an expression.Is this a callback or being passed as an argument?
→ Yes? Use an expression (or arrow function).Am I conditionally creating this function?
→ Yes? Use an expression.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:
Arrow Functions → Learn the even shorter syntax and how
thisbinding works differentlyClosures → Understand how functions remember their surrounding scope
Higher-Order Functions → Use functions that accept or return other functions (
map,filter,reduce)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
thisbehaves differently and when it mattersClosures 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! 🚀




