JavaScript
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax and Types
-
Section 3: Strings and Numbers in Depth
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Objects and Arrays
-
Section 7: Maps, Sets, and Symbols
-
Section 8: Asynchronous JavaScript
-
Section 9: Object-Oriented and Prototypes
-
Section 10: The DOM
-
Section 11: Browser APIs
-
Section 12: Modern JavaScript (ES2015-ES2025)
-
Section 13: Functional Programming Patterns
-
Section 14: Error Handling and Debugging
-
Section 15: Testing
-
Section 16: Accessibility for JavaScript Developers
-
Section 17: Internationalization and Localization
-
Section 18: Performance
-
Section 19: Node.js Fundamentals
-
Section 20: Regular Expressions
-
Section 21: Design Patterns in JavaScript
-
Section 22: Security Basics
-
Section 23: Data Structures and Algorithms in JavaScript
-
Section 24: Practical Projects
-
Section 25: More Advanced Async Patterns
-
Section 26: More Object and Class Practice
-
Section 27: Working with Dates and Internationalization
-
Section 28: Web Components
-
Section 29: More DOM and Browser Practice
-
Section 30: Build Tooling for Vanilla JavaScript
-
Section 31: More Practice Projects
-
Section 32: Interview and Algorithm Practice
-
Section 33: Error Objects (MDN Reference)
-
Section 34: TypedArrays and Binary Data
-
Section 35: Reflection and Metaprogramming (MDN Reference)
-
Section 36: More Global Functions (MDN Reference)
28: Function Declarations vs Expressions
If you've been writing JavaScript for a few weeks, you've probably noticed there are two main ways to create a function. You can use a declaration—the classic function doSomething() {}—or an expression, where you assign a function to a variable, like const doSomething = function() {}. At first glance, they look like two different ways to achieve the same result. I'll let you in on a secret: they aren't the same, and using the wrong one in a large codebase is a great way to introduce bugs that are a nightmare to debug.
The "Magic" that confuses new devs
The biggest difference is something called hoisting. When you use a function declaration, JavaScript moves the entire function definition to the top of its scope during the compilation phase. To a beginner, this looks like a superpower. You can call a function on line 5, even though you didn't actually write the function until line 50.
// This works perfectly, which is actually the problem
const total = calculateTax(100);
function calculateTax(amount) {
return amount * 1.15;
}
I used to think this was convenient, but in a professional environment, it's a trap. When I'm reviewing a pull request and I see a function call at the top of a 200-line file, I have to go hunting through the code to find where that logic actually lives. It breaks the natural "top-to-bottom" flow of reading a script. Worse, if you accidentally declare another function with the same name later in the file, JavaScript will just overwrite the first one without warning you. It's far too permissive.
Locking it down with expressions
The "better" way—the way we actually write production code—is to use function expressions, usually paired with const. Because you're assigning the function to a variable, it follows the same rules as any other variable: it isn't hoisted. If you try to call it before the line where it's defined, the engine will throw a ReferenceError.
// This will crash, and that's a good thing
const total = applyDiscount(100);
const applyDiscount = function(amount) {
return amount * 0.90;
};
By forcing the function to be defined before it's used, you're making the dependency graph of your code explicit. You can read the file from top to bottom and know exactly what is available at any given moment. Plus, using const ensures that you can't accidentally reassign that function to something else later in your script. It turns a runtime mystery into a predictable structure.
When expressions become powerful
Beyond just avoiding hoisting, expressions open the door to patterns that declarations simply can't handle. Since a function expression is just a value, you can pass it as an argument to another function. This is the bread and butter of JavaScript. Whether you're using .map(), .filter(), or adding an event listener to a button, you're using expressions.
You've likely seen arrow functions (() => {}) by now. Those are just a concise syntax for function expressions. You can't "hoist" an arrow function. This forces a discipline in your architecture: you define your helper utilities first, and your main execution logic last. It sounds like a small detail, but when you're working on a team with five other engineers, that predictability is the only thing keeping the project from collapsing into chaos.
📋 Practical Task
Refactoring the Loyalty Point Calculator
You've been handed a legacy script for a loyalty points system. The previous developer relied heavily on function declarations and hoisting, making the code hard to follow. Your task is to refactor the code to use const function expressions.
Requirements:
- Convert the
calculatePointsandformatPointsMessagefunction declarations into function expressions usingconst. - Rearrange the code so that the functions are defined before they are called.
- Ensure the final output is logged to the console correctly.
// REFACTOR THIS CODE
console.log(formatPointsMessage(calculatePoints(500)));
function calculatePoints(spend) {
return spend * 0.1;
}
function formatPointsMessage(points) {
return `You have earned ${points} loyalty points!`;
}There are no comments for now.