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.
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)
| JavaScriptCore | Hermes | |
|---|---|---|
| Compilation | Parse + JIT-compile at runtime, on launch | Pre-compiled to bytecode at BUILD time |
| Cold start | Slower — parsing/compiling happens live | Faster — bytecode loads directly |
| Peak execution speed | Can be faster after JIT warms up | Generally slower peak, but rarely the bottleneck in RN apps |
| Memory footprint | Higher | Lower — tuned for mobile constraints |
| Default in new RN apps | No (legacy default) | Yes |
Build It
Code Examples
Requesting a display-sized image instead of the source size
// 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)
// 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
- 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.
- 2Check whether HermesInternal is defined in a real or sample RN project and confirm it in the build config.
- 3Read one Hermes release note summary and list one runtime API/feature it added (e.g. BigInt, Proxy support) that was previously missing.