Learning Hermes debugging and profiling: connecting to Chrome DevTools and Flipper, profiling JavaScript execution, taking heap snapshots, and recognizing performance pitfalls such as re-renders, large objects, and sync loops.

In episode 6 you understood how the Hermes garbage collector works and the habits that keep memory healthy. Now it's time to put that understanding to work solving real problems: debugging and profiling. This episode teaches you how to connect Hermes to Chrome DevTools and Flipper, profile JavaScript execution, take heap snapshots, and recognize the most common performance pitfalls in React Native apps — re-renders, large objects, and sync loops.
The goal isn't just being able to read numbers, but being able to answer the question that comes up most often in development: "why is this app slow?" With the right tools, the answer changes from a guess to evidence.
Hermes implements an inspection protocol compatible with the Chrome DevTools Protocol (CDP). That means the debugger you normally use for the web can also attach directly to the Hermes runtime — breakpoints, step-through, and console logging all work.
Run the bundler, then open the app in the emulator or on a device:
npx react-native startIn the app, press d to open the developer menu, then choose the debugger menu. Hermes will connect the app to DevTools through Metro, and you can start interacting with the Sources, Console, and Performance tabs in Chrome. Set a breakpoint in the index.js file or any component — when the app reaches that point, execution pauses and you can inspect the scope, call functions in the console, or step through line by line.
Besides the menu, modern React Native also supports a direct debug mode by pressing j to open the Hermes debugger in Metro. Both use the same inspection path.
Flipper is a desktop debugging platform for mobile apps, and it has first-class support for Hermes. Install Flipper, then install the Hermes plugin inside it. Once connected to the app, Flipper shows app logs, network requests, and most usefully: the Hermes debugger panel for breakpoints plus a memory tab to view the heap.
Flipper's advantage over the browser DevTools is its proximity to native data — view layouts, databases, and preferences can all be inspected in one place. For pure JavaScript issues, DevTools remains more comfortable; for issues at the JS-native boundary, Flipper excels. They aren't rivals; they're complementary.
Once functional debugging is done, it's time for performance debugging. The easiest tool for measuring JavaScript execution is the Performance tab in DevTools. Start a recording, interact with the app, then stop — DevTools shows a flame chart of where execution time went along with the functions consuming the most CPU.
For a quick inline measurement, use performance.now() around the suspicious part of your code:
const start = performance.now();
const data = transformBigPayload(rawPayload);
console.log("transformBigPayload:", performance.now() - start, "ms");If you want to group several measurement points at once, Hermes supports a built-in profiling API:
console.profile("parse-data");
const result = parseCSV(csvText);
console.profileEnd("parse-data");The console.profile and console.profileEnd pair marks execution segments that appear as separate blocks in the profiler. This is handy for comparing two implementations: run profiling on the old and new implementation, then compare the segment durations.
The Memory tab in DevTools for Hermes can take a heap snapshot — a portrait of all live JavaScript objects along with their inter-object relationships. The workflow:
Look for objects whose count rose significantly in the second snapshot and never drops after closing the screen. Open the Dominators panel to find large objects holding memory. From there, inspect the retention path — the chain that prevents that object from being GC'd — and you'll find the culprit: maybe an unsubscribed listener, an uncleaned timer, or a data cache growing without bound.
These three patterns are behind the majority of performance complaints in Hermes-based React Native apps.
Unnecessary re-rendering makes component functions run over and over, creating new objects each time. The classic symptom is a new object or function created inline inside the render, so descendants always detect "props changed":
const ItemList = ({ items }) => {
const handlePress = (id) => {
navigateTo(`/item/${id}`);
};
return items.map((item) => (
<ItemRow key={item.id} item={item} onPress={handlePress} />
));
};Every render of ItemList creates a new handlePress function, and ItemRow using React.memo fails to reject the render because the prop reference changed. The fix: use useCallback for functions and useMemo for objects passed as props, then wrap expensive components with React.memo.
Putting an entire API response into state, or loading an array of thousands of elements into the render all at once, makes Hermes allocate large objects that press on the heap and slow down the GC. Cut the data early: select only the fields you need, paginate lists, and avoid creating "twin" objects just to change a single field. Here, all of episode 6's memory churn lessons meet render performance.
Heavy JavaScript loops — parsing large JSON, transforming data over tens of thousands of iterations, or unbounded recursion — block the main thread. While the loop runs, no frames are drawn and the app feels frozen.
function sumItems(items) {
let total = 0;
for (let i = 0; i < items.length; i++) {
total += items[i].price * items[i].quantity;
}
return total;
}For small data, loops like this are fine. For large data, chunk it up and process it in stages across several frames, or move it to a worker. The key: the main thread shouldn't work for more than a few milliseconds without a break, or frame rate gets sacrificed.
You now have a complete debugging toolkit: Chrome DevTools for breakpoints and profiling via CDP, Flipper for debugging close to native, performance.now() and console.profile for quick measurements, heap snapshots for hunting leaks, and the radar to recognize re-renders, large objects, and sync loops before they bite.
The essentials to take home:
performance.now(), and console.profile.In the next episode, episode 8, we step up to the production level: Hermes in React Native production. You'll learn the release build flow, enable Hermes through configuration, and analyze startup metrics, bundle size, and memory on the build actually released to users. See you there!