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)
120: Debugging Memory Issues in DevTools
We’ve all been there. You’ve built a feature that feels snappy during development, but after an hour of poking around in the app, you notice the fans on your laptop starting to scream. The UI gets jittery, and if you open the Task Manager, your browser tab is eating 2GB of RAM for a page that should be using 100MB. This is the classic memory leak, and it's one of the most frustrating bugs to squash because the code doesn't actually "crash"—it just slowly suffocates.
The "Guess-and-Check" approach
When I first started dealing with memory issues, my instinct was to play a game of elimination. I'd look at the parts of the code that handled large amounts of data—like a data table or a chat window—and start commenting out blocks of logic to see if the memory usage stopped climbing. It's a naive approach because you're essentially guessing. If you have a leak in a utility function that's called from twenty different places, commenting out one feature won't tell you anything.
Another common mistake is thinking that setting a variable to null is a magic bullet. While it can help, it doesn't solve the problem if that object is still being referenced elsewhere. I remember spending a whole afternoon nulling out a large array in a UserSession manager, only to realize the array was still trapped in a closure inside a setInterval callback. The memory stayed allocated because the timer was still ticking, and the timer had a reference to the scope containing that array.
// The kind of mistake that hides from "guess-and-check" function startSessionTracker() { const heavyData = new Array(1000000).fill('🚀'); setInterval(() => { // I'm not even using heavyData here, but because this // closure exists, heavyData can never be garbage collected. console.log("Session still active..."); }, 1000); }Hunting the Retainers in the Heap
The professional way to handle this is to stop guessing and start looking at the Heap. In Chrome DevTools, the Memory tab is your best friend, but it's intimidating because it throws a lot of terminology at you. The trick is to use Heap Snapshots to find what's actually sticking around.
Instead of looking at the total memory, I want you to focus on the "Comparison" view. You take a snapshot, perform the action that you suspect is leaking (like opening and closing a modal ten times), and then take a second snapshot. When you switch the view from "Summary" to "Comparison," DevTools shows you exactly what objects were created between those two points and weren't cleaned up. This narrows your search from "everything in the app" to "the specific objects that are leaking."
But seeing that
(array)or(system / Context)is leaking isn't enough. You have to look at the Retainers panel at the bottom. This is the "smoking gun." The retainers list shows you the chain of references that is preventing the Garbage Collector (GC) from reclaiming that memory. If you see a path that leads back to a global variable or a forgotten event listener, you've found your leak.The trade-off: Precision vs. Overhead
Now, you might wonder why we don't just keep the Memory profiler running all the time. The trade-off is performance. Taking a heap snapshot literally pauses the JavaScript execution engine to walk through every single object in memory. It's a "stop-the-world" event. If you do this in a production environment, your users will see the app freeze for several seconds.
This is why we use snapshots for deep dives, but use the Allocation Instrumentation on Timeline for spotting trends. The timeline view shows you blue bars as memory is allocated. If you see a staircase pattern—where memory goes up, drops slightly, then goes up higher—you know you have a leak. You don't know what is leaking yet, but you know when it's happening. I usually start with the timeline to find the trigger, and then use the snapshot to find the culprit.
📋 Practical Task
Fixing the Leaky Dashboard Event Registry
You have been handed a legacy dashboard module that suffers from a severe memory leak. The module uses a Registry object to track active widgets. Every time a widget is created, it adds a listener to the window object, but the cleanup logic is flawed.
The Goal:
Modify the provided code so that when destroyWidget() is called, the widget is fully eligible for garbage collection. You must ensure that both the internal Registry map and the window event listeners are properly cleared.
const Registry = { activeWidgets: new Map(), }; function createWidget(id) { const widget = { id, data: new Array(100000).fill('Data Point'), onResize: () => { console.log(`Widget ${id} resizing...`); } }; // Store widget in registry Registry.activeWidgets.set(id, widget); // Add global listener window.addEventListener('resize', widget.onResize); return widget; } function destroyWidget(id) { const widget = Registry.activeWidgets.get(id); if (widget) { // TODO: Fix the leak here. // Currently, the widget is removed from the map, // but the window listener still holds a reference to widget.onResize. Registry.activeWidgets.delete(id); } } // Simulation: Creating and destroying widgets repeatedly for(let i = 0; i < 100; i++) { const w = createWidget(i); destroyWidget(i); }Verification: If you run this in Chrome, take a Heap Snapshot before the loop and after the loop. If the
(array)objects from the widgets are still present in the snapshot afterdestroyWidgethas been called, the leak persists.
There are no comments for now.