Advanced90 min read

Advanced JavaScript: Memory Management and Performance

Deep dive into JavaScript memory management, garbage collection, memory leaks, and performance optimization techniques.

Topics Covered:

Memory ManagementGarbage CollectionMemory LeaksPerformanceProfiling

Prerequisites:

  • JavaScript Closures and Scope Deep Dive

Video Tutorial

Overview

Understanding JavaScript memory management is crucial for building performant applications. This tutorial covers how JavaScript manages memory, garbage collection, common memory leaks, performance optimization techniques, and profiling tools. This knowledge is essential for building large-scale React applications that perform well.

JavaScript Memory Management

JavaScript automatically manages memory through garbage collection. Understanding how this works helps you write efficient code. Memory Lifecycle: • Allocation - Memory is allocated when needed • Use - Memory is used by your program • Release - Memory is freed when no longer needed Garbage Collection: • Automatic memory management • Marks and sweeps unreachable objects • Runs periodically • Can cause performance hiccups

Code Example:
// Memory allocation
let obj = { name: "Alice" }; // Memory allocated
obj = null; // Memory can be garbage collected

// Memory leaks - global variables
// ❌ BAD: Global variable never freed
window.data = new Array(1000000).fill(0);

// ✅ GOOD: Local variable
function processData() {
  const data = new Array(1000000).fill(0);
  // data is garbage collected after function ends
}

// Memory leaks - closures
// ❌ BAD: Closure holds reference
function createLeak() {
  const largeData = new Array(1000000).fill(0);
  
  return function() {
    // Closure holds reference to largeData even if unused
    console.log("Leak");
  };
}

// ✅ GOOD: Only capture what you need
function createNoLeak() {
  return function() {
    console.log("No leak");
  };
}

// Memory leaks - event listeners
// ❌ BAD: Event listeners not removed
function addListener() {
  const button = document.getElementById("btn");
  button.addEventListener("click", handleClick);
  // Listener never removed
}

// ✅ GOOD: Remove listeners
function addListener() {
  const button = document.getElementById("btn");
  button.addEventListener("click", handleClick);
  
  return () => {
    button.removeEventListener("click", handleClick);
  };
}

// Memory leaks in React
function Component() {
  const [data, setData] = useState(null);
  
  useEffect(() => {
    const controller = new AbortController();
    
    fetch("/api/data", { signal: controller.signal })
      .then(res => res.json())
      .then(setData);
    
    // ✅ Cleanup prevents memory leaks
    return () => {
      controller.abort();
    };
  }, []);
}

JavaScript automatically manages memory. Avoid memory leaks by: removing event listeners, cleaning up closures, avoiding global variables, and properly cleaning up in React useEffect.

Performance Optimization Techniques

Optimize JavaScript performance through various techniques: debouncing, throttling, lazy loading, and code splitting. Optimization Strategies: • Debounce/throttle event handlers • Lazy load resources • Code splitting • Memoization • Virtual scrolling • Web Workers for heavy computation

Code Example:
// Debouncing (wait for pause)
function debounce(func, delay) {
  let timeoutId;
  return function(...args) {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => func.apply(this, args), delay);
  };
}

const debouncedSearch = debounce((query) => {
  console.log("Searching:", query);
}, 300);

// Throttling (limit execution frequency)
function throttle(func, limit) {
  let inThrottle;
  return function(...args) {
    if (!inThrottle) {
      func.apply(this, args);
      inThrottle = true;
      setTimeout(() => inThrottle = false, limit);
    }
  };
}

const throttledScroll = throttle(() => {
  console.log("Scrolled");
}, 100);

// Memoization
function memoize(fn) {
  const cache = {};
  return function(...args) {
    const key = JSON.stringify(args);
    if (cache[key]) {
      return cache[key];
    }
    const result = fn.apply(this, args);
    cache[key] = result;
    return result;
  };
}

const expensiveFunction = memoize((n) => {
  // Expensive calculation
  return n * n;
});

// Lazy loading
const LazyComponent = React.lazy(() => import('./HeavyComponent'));

// Code splitting
import('./module').then(module => {
  module.doSomething();
});

Optimize performance with debouncing, throttling, memoization, lazy loading, and code splitting. These techniques reduce unnecessary work and improve user experience.

Conclusion

Memory management and performance optimization are crucial for building scalable applications. You've learned about garbage collection, memory leaks, and optimization techniques. Apply these concepts in your React applications to ensure they perform well at scale.