Course:Internet & Tools/
Lesson

The component works. The data loads. The layout looks right. But when you type in the search box, every keystroke lags. When you scroll the list, the page stutters. When you leave the tab open for an hour, it uses 2GB of memory. These are performance problems, and AI tools are notorious for introducing them because they optimize for "code that works" rather than "code that runs efficiently."

The Performance panel reveals exactly what the browser is doing, and wasting time on, during any interaction.

Recording a performance profile

The workflow is: start recording, do the thing that feels slow, stop recording.

  1. Open the Performance panel (DevTools → Performance tab)
  2. Click the circle (Record) button, or press Cmd+E / Ctrl+E
  3. Perform the slow interaction (scroll, type, click, navigate)
  4. Click Stop (or press Cmd+E again)
  5. Wait a few seconds while DevTools analyzes the recording

Keep recordings short, 3 to 5 seconds. Longer recordings are harder to analyze and generate huge flame charts.

// If you want to profile something that happens on page load:
// Check "Screenshots" in the Performance panel toolbar
// Click the reload button (circle-arrow icon) instead of Record
// DevTools will record from the moment the page starts loading
02

Reading the flame chart

The flame chart is the main visualization. Here is how to interpret it:

Visual elementWhat it means
Bar widthDuration, wider bars took longer to execute
Yellow barsJavaScript execution
Purple barsRendering (layout calculations, style recalculation)
Green barsPainting (drawing pixels to the screen)
Stacked barsCall stack, bars below were called by the bar above
Red triangle on a barLong task warning, this task blocked the main thread for over 50ms

The strategy: find the widest yellow bars (long JavaScript tasks), click on them to see the function name, then look at the stacked bars underneath to understand what was being called.

The 50ms threshold matters because browsers target 60 frames per second, giving each frame about 16.6ms. Any single task over 50ms will miss multiple frames, causing visible stuttering. AI tools do not think about frame budgets, they write code that "works" regardless of how long it takes per frame.
03

Common AI-generated performance problems

Problem 1: re-rendering on every keystroke

This is the single most common performance issue in AI-generated React code. ChatGPT and Copilot routinely generate components that trigger a full re-renderWhat is re-render?When React calls a component function again to check if the UI needs updating, triggered by state or context changes. of a large list on every keystroke in a search input.

// Copilot generated this - it re-renders 1000 items per keystroke
function SearchableList({ items }) {
  const [query, setQuery] = useState('');

  // This filters on every render - fine
  const filtered = items.filter(item =>
    item.name.toLowerCase().includes(query.toLowerCase())
  );

  return (
    <div>
      <input
        value={query}
        onChange={e => setQuery(e.target.value)}
      />
      {/* Problem: rendering 1000 items on every keystroke */}
      {filtered.map(item => (
        <ExpensiveItemCard key={item.id} item={item} />
      ))}
    </div>
  );
}

In the Performance panel, you would see a tall stack of yellow bars firing every 50-100ms (once per keystroke), each containing hundreds of ExpensiveItemCard render calls. The fix involves useMemo, React.memo, or debouncing the input.

// Fixed version - memoize filtered results and card rendering
function SearchableList({ items }) {
  const [query, setQuery] = useState('');

  const filtered = useMemo(
    () => items.filter(item =>
      item.name.toLowerCase().includes(query.toLowerCase())
    ),
    [items, query]
  );

  return (
    <div>
      <input
        value={query}
        onChange={e => setQuery(e.target.value)}
      />
      {filtered.map(item => (
        <MemoizedItemCard key={item.id} item={item} />
      ))}
    </div>
  );
}

const MemoizedItemCard = React.memo(ExpensiveItemCard);

Problem 2: forced synchronous layout

AI-generated code frequently reads a layout property (like offsetHeight) immediately after writing a style, forcing the browser to recalculate layout synchronously instead of batchingWhat is batching?Grouping multiple state updates together into a single re-render cycle instead of re-rendering after each individual update..

// Claude generated this animation - it causes layout thrashing
function animateElements(elements) {
  for (const el of elements) {
    const height = el.offsetHeight;       // Read - forces layout
    el.style.marginTop = height + 'px';   // Write - invalidates layout
    // Next iteration: read forces ANOTHER layout calculation
  }
}

// Fixed: batch reads first, then writes
function animateElements(elements) {
  const heights = elements.map(el => el.offsetHeight); // All reads
  elements.forEach((el, i) => {
    el.style.marginTop = heights[i] + 'px';            // All writes
  });
}

The flame chart shows this as "Forced reflow" in purple, often appearing hundreds of times in a loop. Each one takes 1-5ms, but in a loop of 100 elements, that is 100-500ms of total blocking time.

Problem 3: memory leaks from missing cleanup

AI tools consistently forget to clean up event listeners, intervals, and subscriptions. The code works on first load, but memory usage grows every time the component re-mounts.

// ChatGPT generated this React component - it leaks memory
function LiveClock() {
  const [time, setTime] = useState(new Date());

  useEffect(() => {
    // BUG: interval is never cleared!
    setInterval(() => setTime(new Date()), 1000);
  }, []);

  return <span>{time.toLocaleTimeString()}</span>;
}

// Fixed: return a cleanup function
function LiveClock() {
  const [time, setTime] = useState(new Date());

  useEffect(() => {
    const id = setInterval(() => setTime(new Date()), 1000);
    return () => clearInterval(id); // Cleanup on unmount
  }, []);

  return <span>{time.toLocaleTimeString()}</span>;
}

To detect memory leaks: open the Memory panel, take a heap snapshot, interact with your app (navigate away and back several times), take another snapshot, then compare them. If the same type of object keeps growing in count, that is your leak.

After pasting any AI-generated useEffect that sets up listeners, timers, or subscriptions, immediately check: does it return a cleanup function? If not, it will leak. This is the single most common AI React bug.
04

Lighthouse, automated performance audits

Lighthouse is an automated auditing tool built into DevTools. It loads your page in a controlled environment and scores it across multiple categories:

MetricWhat it measuresGood score
FCP (First Contentful Paint)When the user first sees contentUnder 1.8s
LCP (Largest Contentful Paint)When the main content finishes loadingUnder 2.5s
TBT (Total Blocking Time)How long the main thread was blockedUnder 200ms
CLS (Cumulative Layout Shift)How much content jumps around during loadUnder 0.1
Speed IndexHow quickly the visible area is populatedUnder 3.4s

Run Lighthouse in an Incognito window to avoid interference from browser extensions. AI-generated bundleWhat is bundle?A single JavaScript file (or set of files) that a build tool creates by combining all your source code and its imports together. sizes are often larger than necessary because AI imports entire libraries when only one function is needed, Lighthouse will flag this under "Reduce unused JavaScript."

05

Quick reference

ToolWhere to find itWhat it reveals about AI code
Performance panelDevTools → PerformanceRe-renders, layout thrashing, long tasks
Flame chartInside a Performance recordingWhich functions are slow and what they call
Memory panelDevTools → MemoryLeaked event listeners, uncleaned intervals
LighthouseDevTools → LighthouseBundle size, load speed, unused code
React DevTools ProfilerReact DevTools extension → ProfilerWhich React components re-render and why