RoadmapDay 39 / 80
React NativeMonth 2 · Week 8

Day 39: Image Optimization, Memory Profiling & Hermes Engine

Understand where mobile memory actually goes (images, mostly) and how Hermes changes JS startup and runtime memory characteristics.

Mark this day complete

Study

Concepts

Images are usually the biggest memory consumer in a mobile app

A decoded image in memory costs roughly `width × height × 4 bytes` (RGBA), completely independent of the file's compressed size on disk — a 4000×3000 JPEG that is only 500KB on disk can decode to ~48MB in memory. Always request/resize images to the DISPLAY size needed (not the source size) before or during loading — `resizeMode`/`resizeMethod` and server-side or CDN-based image resizing (e.g. requesting a `?w=400` variant) are the standard fixes, and libraries like `react-native-fast-image` add disk+memory caching on top.

A screen rendering many large images (a photo grid, a chat with image bubbles) without recycling or downsizing is the single most common cause of RN memory warnings and crashes on lower-end devices — profile this BEFORE assuming a JS-level leak is the culprit.

Hermes: a JS engine built for RN's constraints

Hermes compiles JS to bytecode AHEAD OF TIME (at build time), rather than parsing and JIT-compiling JS on every app launch like JavaScriptCore — this significantly improves cold-start time (TTI) because the work of parsing/compiling is already done before the app even ships, at the cost of no runtime JIT (Hermes prioritizes startup and memory over raw peak execution speed, a good tradeoff for a mobile app that starts often and runs relatively short-lived JS logic per session compared to a long-running server process).

Hermes also has a memory-optimized garbage collector and smaller heap overhead tuned for mobile constraints, and (since it is JSI-based, Day 32) is the default engine for new React Native apps on both platforms today.

See It

Visualizations

Visualization

JavaScriptCore (JIT) vs Hermes (AOT bytecode)

 JavaScriptCoreHermes
CompilationParse + JIT-compile at runtime, on launchPre-compiled to bytecode at BUILD time
Cold startSlower — parsing/compiling happens liveFaster — bytecode loads directly
Peak execution speedCan be faster after JIT warms upGenerally slower peak, but rarely the bottleneck in RN apps
Memory footprintHigherLower — tuned for mobile constraints
Default in new RN appsNo (legacy default)Yes

Build It

Code Examples

Requesting a display-sized image instead of the source size

jsx
// Bad: downloads and decodes the FULL source resolution
// (e.g. 4000x3000) to display in a 100x100 thumbnail.
<Image source={{ uri: photo.originalUrl }} style={{ width: 100, height: 100 }} />

// Better: request an appropriately-sized variant from your CDN/backend,
// and let a caching image library handle memory efficiently.
import FastImage from 'react-native-fast-image';

<FastImage
  source={{ uri: `${photo.originalUrl}?w=200&h=200&fit=cover` }}
  style={{ width: 100, height: 100 }}
  resizeMode={FastImage.resizeMode.cover}
/>

Confirming Hermes is enabled (RN 0.70+, default)

js
// android/app/build.gradle
project.ext.react = [
  enableHermes: true, // explicit on older RN versions; default true on newer ones
]

// Runtime check from JS:
const isHermes = () => !!global.HermesInternal;
console.log('Using Hermes:', isHermes());

Remember

Key Takeaways

  • A decoded image costs width × height × 4 bytes in memory — the FILE size on disk is irrelevant to that cost.
  • Always request/display images sized for their actual on-screen dimensions, not their original source resolution.
  • Hermes precompiles JS to bytecode at build time, trading peak JIT execution speed for much faster cold start and lower memory use.
  • Hermes is the default engine for modern React Native apps and is required groundwork for the JSI-based New Architecture.
  • When debugging RN memory crashes, profile image/memory usage FIRST — it is the most common real-world cause, not a JS leak.

Do It

Practice

  1. 1Load a large source image at a small display size, inspect memory usage in Xcode Instruments / Android Studio Profiler, then fix it with a resized variant and re-measure.
  2. 2Check whether HermesInternal is defined in a real or sample RN project and confirm it in the build config.
  3. 3Read one Hermes release note summary and list one runtime API/feature it added (e.g. BigInt, Proxy support) that was previously missing.