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)
118: Common JavaScript Pitfalls and Gotchas
By the time you've reached this point in the course, you're likely comfortable with the syntax of JavaScript. But here is the truth: JavaScript has a habit of behaving in ways that seem completely illogical if you're coming from a language like Java or C#. I've spent way too many late nights staring at a debugger wondering why a variable changed its value when I "didn't touch it," only to realize I'd fallen into one of the language's classic traps. These aren't bugs in the language—they're features of its design—but they'll bite you if you aren't looking for them.
The Trap of Shared References vs. True Copies
One of the most frequent mistakes I see is how developers handle objects and arrays. In JavaScript, objects are passed by reference, not by value. If you've ever tried to "clone" a user settings object to make a temporary change, you might have done something like this:
const defaultSettings = { theme: 'dark', notifications: { email: true, sms: false } };
const userSettings = defaultSettings;
userSettings.theme = 'light';
console.log(defaultSettings.theme); // 'light' - Wait, what?
The naive approach here is assuming that = creates a new object. It doesn't. It creates a new pointer to the same spot in memory. Even if you use the spread operator (...), you're only doing a shallow copy. If you have nested objects, like the notifications object above, the spread operator only copies the reference to that nested object. If you change userSettings.notifications.email = false, you've just changed it for the defaultSettings too.
The better way, depending on your needs, is to use a deep clone or a dedicated immutability pattern. For most modern projects, structuredClone() is now the gold standard for deep copying. It handles nested structures without the weird quirks of JSON.parse(JSON.stringify()) (which destroys dates and functions). I usually tell my juniors: unless you have a very specific performance reason to mutate an object, treat your data as immutable. It eliminates an entire category of "how did that value get there?" bugs.
Losing the Thread with 'this' Context
Then there's the this keyword. It's probably the most misunderstood part of the language. The "gotcha" usually happens when you pass a class method as a callback to something else, like a setTimeout or an event listener.
class Timer {
constructor() {
this.seconds = 0;
}
tick() {
this.seconds++;
console.log(this.seconds);
}
start() {
setInterval(this.tick, 1000);
}
}
const myTimer = new Timer();
myTimer.start(); // TypeError: Cannot read property 'seconds' of undefined (or NaN)
What's happening here is that setInterval doesn't care that tick belongs to the Timer class; it just takes the function and executes it later in the global context. By the time tick actually runs, this no longer points to your myTimer instance—it's either undefined (in strict mode) or the window object.
You'll see some people use .bind(this) in the constructor to fix this, but that's a bit verbose. The modern, cleaner approach is to use an arrow function for the method. Arrow functions don't have their own this; they inherit it from the surrounding scope (lexical scoping). If you define tick = () => { ... } as a class property, it will always be bound to the instance of the class, regardless of where it's called from. It costs a tiny bit more memory because the function is recreated for every instance, but in 99% of applications, the trade-off for sanity and predictability is well worth it.
📋 Practical Task
Fixing the Shared User State and Timer Context
You have been handed a buggy piece of code for a User Dashboard. There are two major issues you need to fix:
- The State Leak: When the user clicks "Preview Settings," the app creates a
previewSettingsobject. However, changing a value in the preview is accidentally updating the actualcurrentUserSettingsbecause of a shallow copy/reference bug. - The Context Crash: There is a
SessionManagerclass that uses asetIntervalto update a countdown. Currently, it throws an error becausethisis lost when the interval triggers.
Your Task: Rewrite the provided code to ensure that the preview settings are a deep clone of the original and that the SessionManager maintains the correct this context during its interval ticks.
// BUGGY CODE
const currentUserSettings = {
id: 1,
profile: {
name: "Alex",
preferences: { theme: "dark", lang: "en" }
}
};
function createPreview() {
// TODO: Fix this shallow copy/reference bug
return { ...currentUserSettings };
}
class SessionManager {
constructor() {
this.timeLeft = 60;
}
updateTimer() {
// TODO: This will crash because 'this' is lost
this.timeLeft--;
console.log(`Time remaining: ${this.timeLeft}`);
}
init() {
setInterval(this.updateTimer, 1000);
}
}
// Test the fix
const preview = createPreview();
preview.profile.preferences.theme = "light";
console.log(currentUserSettings.profile.preferences.theme); // Should still be "dark"
const session = new SessionManager();
session.init();
There are no comments for now.