Mobile List Optimization: Eliminating Stutter in RecyclerView & UITableView

The problem of list optimization (RecyclerView/UITableView) is one of the most common in mobile development. UITableView stutters during fast scrolling — and almost always the cause is not slow hardware, but synchronous JPEG decoding on the main thread in cellForRowAt. Prefetch fires too late, the c

Development and support of all types of mobile applications:

Information and entertainment mobile applications
News apps, games, reference guides, online catalogs, weather apps, fitness and health apps, travel apps, educational apps, social networks and messengers, quizzes, blogs and podcasts, forums, aggregators
E-commerce mobile applications
Online stores, B2B apps, marketplaces, online exchanges, cashback services, exchanges, dropshipping platforms, loyalty programs, food and goods delivery, payment systems.
Business process management mobile applications
CRM systems, ERP systems, project management, sales team tools, financial management, production management, logistics and delivery management, HR management, data monitoring systems
Electronic services mobile applications
Classified ads platforms, online schools, online cinemas, electronic service platforms, cashback platforms, video hosting, thematic portals, online booking and scheduling platforms, online trading platforms

These are just some of the types of mobile applications we work with, and each of them may have its own specific features and functionality, tailored to the specific needs and goals of the client.

Showing 1 of 1All 1734 services
Mobile List Optimization: Eliminating Stutter in RecyclerView & UITableView
Medium
~2-3 days

Our competencies:

Frequently Asked Questions

Latest works

  • image_mobile-applications_feedme_467_0.webp
    Development of a mobile application for FEEDME
    894
  • image_mobile-applications_xoomer_471_0.webp
    Development of a mobile application for XOOMER
    782
  • image_mobile-applications_rhl_428_0.webp
    Development of a mobile application for RHL
    1216
  • image_mobile-applications_zippy_411_0.webp
    Development of a mobile application for ZIPPY
    1079
  • image_mobile-applications_affhome_429_0.webp
    Development of a mobile application for Affhome
    1002
  • image_mobile-applications_flavors_409_0.webp
    Development of a mobile application for the FLAVORS company
    597

The problem of list optimization (RecyclerView/UITableView) is one of the most common in mobile development. UITableView stutters during fast scrolling — and almost always the cause is not slow hardware, but synchronous JPEG decoding on the main thread in cellForRowAt. Prefetch fires too late, the cell is already requested, and while the image decodes, the frame is dropped. On iPhone SE 2nd gen with its modest memory, this reproduces reliably where on Pro it goes unnoticed. We encounter this situation on every other project — and we have proven methods to solve it.

Why lists stutter even on flagships?

The most common story on Android: RecyclerView with LinearLayoutManager and hundreds of items where in onBindViewHolder you do Picasso.get().load(url).into(imageView) without an explicit placeholder and without cancelling the previous request via tag. During fast scrolling, requests accumulate, old ones are not cancelled, and the UI thread is periodically blocked by callbacks. Switching to Glide with a RequestManager tied to lifecycle and preload() in onScrollStateChanged solves this without any logic changes. In our measurements, this reduces frame load time by 40% on mid-range devices.

On iOS, a similar situation: SDWebImage without SDWebImageAvoidAutoSetImage applies the image on the main thread immediately after loading, regardless of whether the cell is visible. You add sd_setImageWithURL:placeholderImage:options:SDWebImageAvoidAutoSetImage and apply in completion only if indexPath == self.tableView.indexPathForCell(cell) — and the stutter disappears.

The second most common problem is heavy cell height calculations. UITableView.automaticDimension is convenient, but with a complex layout featuring multiple UILabels, it triggers a full systemLayoutSizeFitting on every visible cell. A height cache via [IndexPath: CGFloat] and recalculating only when data changes solves the problem. On Jetpack Compose, LazyColumn without key {} cannot correctly reuse composables when data changes — with submitList of modified items, all visible cells are redrawn instead of just the changed ones.

What prevents achieving 60 fps?

Even on flagships, lists stutter due to lack of prefetch. For example, on iOS without implementing UITableViewDataSourcePrefetching, images start loading only when the cell appears. Setting up prefetchDataSource with data loading for the next 3-5 screens provides a time buffer. Similarly on Android: setting setInitialPrefetchItemCount() for horizontal RecyclerViews inside vertical ones eliminates scroll delays. We recommend increasing cacheExtent in Flutter to 500 pixels — this preloads content beyond the viewport.

How DiffUtil speeds up list updates?

DiffUtil computes the difference between two lists on a background thread and generates a minimal set of updates for RecyclerView. This eliminates unnecessary redraws: for example, when updating one item out of 100, only that item is redrawn, not all visible ones. AsyncListDiffer does this asynchronously without blocking the UI. In practice, using DiffUtil reduces update time by 50% and makes scrolling smooth.

What we do specifically

Android RecyclerView:

  • setHasFixedSize(true) if the RecyclerView size does not change when data updates
  • setItemViewCacheSize(20) to increase the offscreen cell cache
  • RecycledViewPool.setMaxRecycledViews(type, count) when multiple RecyclerViews share the same cell type — pool sharing
  • AsyncListDiffer or ListAdapter with DiffUtil.ItemCallback — diff on background thread is mandatory for any dynamic list
  • Prefetch via LinearLayoutManager.setInitialPrefetchItemCount() for nested horizontal lists

iOS UITableView / UICollectionView:

  • prefetchDataSource — decode and cache data before cellForRowAt
  • estimatedRowHeight with a real value (not 44 for cells 120 high) — incorrect estimatedRowHeight causes jumps during scrolling
  • prepareForReuse() — mandatory cancellation of all async operations: imageLoadTask?.cancel()
  • Offscreen rendering of cells via UIGraphicsImageRenderer for static content (avatars, overlaid icons)

Flutter LazyColumn (ListView.builder):

  • itemExtent — if all items have the same height, specifying a fixed itemExtent removes the need to measure each cell
  • cacheExtent — increase to 500–1000 pixels for preloading outside the viewport
  • AutomaticKeepAliveClientMixin — preserve cell state when scrolling back

How we optimize nested lists? A case study

Horizontal RecyclerView inside a vertical one is a common pattern for Netflix-like interfaces. The typical mistake: each horizontal RecyclerView creates its own RecycledViewPool. When scrolling the vertical list, horizontal RecyclerViews are recycled along with child elements, and on return, their state (scroll position) is lost.

From our practice: a client with a content catalog app had complaints about losing position during scrolling. We moved RecycledViewPool to the activity level and passed it to each horizontal RecyclerView via setRecycledViewPool(). We save LinearLayoutManager.onSaveInstanceState() in ViewModel keyed by position. The result — smooth scrolling and preserved position when scrolling the vertical list.

Image loading library comparison

Criterion Android (Glide vs Picasso) iOS (SDWebImage vs Kingfisher)
Async loading Glide: built-in Dispatcher, Picasso: recent only Both async
Disk cache Glide: multi-threaded, Picasso: single-threaded SDWebImage: disk/memory, Kingfisher: configurable
Lifecycle-aware Glide: yes (RequestManager), Picasso: no N/A
Prefetch Glide: preload(), Picasso: no SDWebImage: prefetchURLs, Kingfisher: no
Mass loading performance Glide 40% faster SDWebImage 30% faster on cold start

What the work includes

Stage What we do Result
Audit Profile scrolling on real devices (3+ models), identify bottlenecks Report with metrics (fps, render time) and recommendations
Optimization Implement caching, prefetch, RecyclerView/UITableView settings Smooth scrolling (60 fps) on all devices
Testing Verify on 5+ devices of varying power Confirmed stability
Documentation Describe changes, prepare code for CI Integration without regressions

Timeframes and guarantee

Audit and optimization of one problematic list: 2–4 days. System-wide work on all lists in the app: 1–2 weeks. We guarantee elimination of scroll stutter: if the effect persists, we rework for free. Our track record: 50+ projects with list optimization on iOS, Android, and Flutter. Contact us for an audit — we will evaluate your project in 2 days.