A screen that renders instantly in the simulator can still stutter on a mid-range Android device the moment it scrolls past a dozen product photos, and the cause is almost never the JavaScript. It is the native image pipeline: every image on screen has to be fetched or read from disk, decoded from a compressed format into raw pixel data, and held in memory at its decoded size, which for a full-resolution photo can be tens of megabytes even though the on-screen thumbnail is a few hundred pixels wide. A feed that decodes twenty full-resolution photos to display twenty thumbnails is not a rendering problem to fix with re-render discipline — it is a decoding and memory problem that lives entirely outside the component tree this course has spent several lessons optimizing.
The failure mode is specific and repeatable: memory climbs as a list scrolls, frame drops appear right as new images enter view, and on lower-memory Android devices the OS eventually kills the app in the background because it holds more decoded image data than the device is willing to keep resident. None of this shows up in a profiler that only watches JavaScript execution, because the decode work happens on the native side, off the JS thread, which is exactly why teams that never learned to reason about image cost ship apps that pass every component-level performance review and still get one-star reviews for lagging on a product grid.
This lesson treats an image as a resource with a real, quantifiable cost — network bytes, decode time, and resident memory — and treats every choice about it (source, size, format, caching, and when it loads) as a decision that trades one of those costs against another. Getting this right is what separates a feed that feels instant from one that feels like it is fighting the device for every scroll.