JavaScript Event Listeners — Spot the Memory Leak Bug
This is a daily Javascript challenge from the CodeShot archive. Practice your knowledge of Duplicate Event Listener Bug and improve your technical interview readiness.
function addClickHandler() {
document.getElementById("btn")
.addEventListener("click", () => {
console.log("clicked")
})
}
// Called on every render
addClickHandler()
Detailed Explanation
Why This Question Matters
If you've spent any time working with vanilla JavaScript or early versions of React/Vue, you've probably run into a weird bug where a single button click triggers an action five times. You check your code, you see one addEventListener call, and you're left scratching your head.
This is a classic memory leak and performance trap. The issue isn't that the code is "broken" in the sense that it crashes; it's that it behaves unpredictably. Understanding how the browser handles event listeners is the difference between a smooth app and one that feels sluggish or triggers duplicate API calls every time a user clicks a button.
Understanding the Code
Let's look at the snippet:
At first glance, it looks fine. You have a function that grabs a button and attaches a click listener. Simple.
The problem is the comment: // Called on every render.
In a real application—especially one using a component-based architecture—functions like this are often called inside a loop, a render cycle, or a state update. Every time addClickHandler() runs, the browser doesn't check if the listener already exists. It just says, "Cool, another function to run when this button is clicked," and adds it to the internal list of listeners for that element.
If this function runs 10 times, you now have 10 identical anonymous functions sitting in memory, all waiting to fire. When the user clicks the button once, you'll see "clicked" printed 10 times in the console.
Finding the Correct Answer
The core of the problem is that anonymous functions are unique.
When you pass an arrow function () => { ... } directly into addEventListener, you are creating a new function object in memory every single time that line of code executes.
Even if the code inside the function is identical, JavaScript treats these as different entities. Because the browser only ignores duplicate listeners if the function reference is exactly the same, these anonymous functions keep piling up.
To fix this, you have a few options:
1. Named Functions: Define the handler outside the render loop and pass the reference.
2. Removal: Call removeEventListener before adding a new one (though this requires a named function).
3. Once: Use the { once: true } option if the event should only happen once.
4. Logic Guard: Only call the attachment function once during the initial app mount.
Common Mistakes Developers Make
The most common mistake is assuming that addEventListener behaves like a property assignment (e.g., element.onclick = ...).
If you use element.onclick, you are overwriting the previous value. Only one function can be assigned to onclick. However, addEventListener is designed specifically to allow *multiple* listeners for the same event. While this is powerful, it's also where the "duplicate listener" bug lives.
Another trap is thinking that clearing the innerHTML of a parent element automatically cleans up all listeners. While modern browsers are pretty good at garbage collecting listeners when an element is removed from the DOM, relying on this blindly can lead to memory leaks in complex single-page applications (SPAs).
Real-World Usage
In a production environment, this usually happens in "Component Mount" logic.
Imagine you're building a custom dropdown. You add a listener to the window to detect clicks outside the dropdown so you can close it. If your component re-renders due to a state change and you call that "init" function again, you've just leaked another global event listener.
If a user spends an hour on your page and the component re-renders 50 times, you now have 50 listeners firing on every single window click. This will eventually tank the frame rate and make the UI feel janky.
The professional way to handle this is to store the function reference and clean it up:
Key Takeaways
addEventListener does not overwrite; it appends.Why this matters
Understanding Duplicate Event Listener Bug is crucial for passing technical interviews. In real-world applications, this concept often leads to subtle bugs if not handled correctly. For more details, you can always refer to the official MDN Documentation.