Skip to Content
Course content

118: Common JavaScript Pitfalls and Gotchas

Click on the "Edit" button in the top corner of the screen to edit your slide content.

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 previewSettings object. However, changing a value in the preview is accidentally updating the actual currentUserSettings because of a shallow copy/reference bug.
  • The Context Crash: There is a SessionManager class that uses a setInterval to update a countdown. Currently, it throws an error because this is 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();
Rating
0 0

There are no comments for now.

to be the first to leave a comment.