Change:
untrackFibersSet.add(fiber); <ins> // React may detach alternate pointers during unmount; // Since our untracking code is async, we should explicily track the pending alternate here as well. const alternate = fiber.alternate; if (alternate !== null) { untrackFibersSet.add(alternate); } </ins> if (untrackFibersTimeoutID === null) { untrackFibersTimeoutID = setTimeout(untrackFibers, 1000); }

Discussion:
! There's seems to be circumstances where unmounted DOM/Fibers are kept alive by React DevTools. They're kept alive in : It seems like a WeakSet would be appropriate and would remove the leak. Otherwise we'd need to understand why recordUnmount isn't called. CC
I'm not necessarily convinced that the above screenshot is showing what this issue suggests. I believe it's possible that this indicates that component rendered where it previously rendered a DOM element, and the "alternate" Fiber still has a reference to the (now unmounted) DOM element. In this case both DevTools and React itself would have references to both Fibers as well as the unmounted DOM element. We do clear pointers for both "current" and "alternate" in but this does not include the property: The retain behavior I describe above can be verified with the following example app code: I'm not sure I would call this a "leak", since the retained DOM elements would go away once the leaf Fiber itself went away, but it could at least exhibit the behavior your Memory profile shows in the picture above. If one was to argue this was a leak, then I believe it would be considered a React leak rather than a DevTools leak. Edit I think we also leak Fibers in this way, since the may have a reference to its previous children - which themselves have references to their Fibers via the "__reactInternalInstance" field.
Quick update about this issue and PR The thing that caused the above temporarily "leak" is the alternate pointer- which will be cleared during the next render. That being said, the reason the temporary "leak" retains so much (the whole subtree) is because we weren't nulling out the field. PR changes this behavior so that we now will null that field. This should make our memory snapshot comparisons less noisy in the future. In other words, hopefully this will show that DevTools isn't actually leaking DOM nodes (and Fibers) but if it is at least we should be able to identify them less ambiguously now.
Looks like there is a memory leak in DevTools, was noticed in Created small demo, it can be observer while scrolling a list, memory increase because of detached fiber and dom elements It happens also in production build, noticed that elements aren't removed from after they are unmounted, something fishy there
Thanks for sharing a repro, I'll take a look.

It does look like DevTools isn't recording unmounts for list-item Fibers in this repro, which causes it to retain them after they've been unmounted. This might have been a bad lead here. I think the reason I'm initially not seeing calls to commit unmount are because grows a pool of items initially. Still digging. It's definitely the list items that are leaking though. Just not sure why yet. Update I'm not convinced this is the entire problem, but it does seem like DevTools isn't properly cleaning out all of the alternate entries in the Map. I think this is because the is being cleared out synchronously by React when a Fiber gets unmounted (as part of our memory cleanup flag) but DevTools is asynchronously processing the untracking in order to support fast Refresh: The previous update is the problem. I think I have a fix. Will be pushing shortly.
Also as side note, i would expect to see similar behaviour in react-window, as in windowing nature there is lot of mounting/unmounting, only difference to react-virtual is, that it's hook based and react-window on classes.
This fix will go out with the next DevTools release.
Thanks