RecyclerView & LazyColumn (Deep Dive)
Implement high-performance lists and grids using RecyclerView in XML and LazyColumn/LazyRow in Jetpack Compose.
Module 9: RecyclerView & LazyColumn (Deep Dive)
Learning Objectives
By the end of this module, you’ll understand:
- Why RecyclerView exists
- Why ListView became obsolete
- RecyclerView architecture
- View recycling
- Adapter
- ViewHolder
- LayoutManager
- ItemDecoration
- DiffUtil
- ListAdapter
- Paging (high level)
- LazyColumn in Compose
- Performance optimization
- Common interview questions
Part 1 – Why RecyclerView Exists
1. The Naive Approach
Imagine showing 10,000 contacts.
A beginner might think:
Contact 1 -> View
Contact 2 -> View
Contact 3 -> View
...
Contact 10000 -> View
That means:
10,000
- TextViews
- ImageViews
- Buttons
- Layouts
Memory usage becomes enormous.
Suppose each row consumes only 20 KB.
20 KB × 10,000
=
200 MB
And that’s only for one screen.
Impossible for a phone.
2. The Key Observation
Question:
How many items are actually visible?
Example:
Phone Screen
↓
15 Items Visible
Not 10,000.
Only around 10–20.
So why create thousands of Views?
Create only what’s visible.
Reuse them.
This idea is recycling.
3. The Bus Analogy
Imagine a city bus.
Passengers get off.
New passengers get on.
The bus isn’t replaced.
Only the passengers change.
RecyclerView works exactly like this.
RecyclerView
↓
View
↓
New Data
↓
Same View
The View survives.
Its displayed data changes.
4. ListView vs RecyclerView
Before RecyclerView, Android had ListView.
Problems:
- Limited animations
- Poor flexibility
- Different APIs for grids
- Harder customization
- Weak update mechanisms
RecyclerView unified these ideas.
| ListView | RecyclerView |
|---|---|
| Vertical list only | Multiple layouts |
| Basic recycling | Advanced recycling |
| No LayoutManager abstraction | LayoutManager |
| Limited animations | Rich animations |
| No DiffUtil | Efficient updates |
Part 2 – RecyclerView Architecture
RecyclerView isn’t one class.
It’s a collaboration of several components.
RecyclerView
│
├── Adapter
├── ViewHolder
├── LayoutManager
├── ItemAnimator
└── ItemDecoration
Each has a single responsibility.
5. RecyclerView
RecyclerView itself does very little.
It coordinates everything.
Think of it as a manager.
Responsibilities:
- Handle scrolling
- Ask Adapter for data
- Recycle Views
- Delegate positioning
- Coordinate animations
It doesn’t know what your items represent.
6. Adapter
The Adapter is the bridge between:
Data
↓
RecyclerView
Suppose you have:
val users = listOf(...)
RecyclerView doesn’t understand User.
Adapter does.
Its job:
- Create item Views
- Bind data
- Report item count
Think of it as a translator.
7. ViewHolder
One of the most important concepts.
Imagine a row:
+-------------------------+
Avatar
Name
Last Message
+-------------------------+
Without ViewHolder:
Every scroll:
findViewById()
findViewById()
findViewById()
Repeated hundreds of times.
Expensive.
Instead:
class UserViewHolder(...)
Holds references once.
ViewHolder
↓
Avatar
↓
Text
↓
Button
No repeated lookup.
8. Adapter Lifecycle
RecyclerView asks the Adapter three important questions.
getItemCount()
Question:
“How many items exist?”
Example:
override fun getItemCount() = users.size
onCreateViewHolder()
Question:
“I need a new View.”
RecyclerView:
Need New Row
↓
Adapter
↓
Inflate XML
↓
Create ViewHolder
Notice:
This is NOT called for every scroll.
Only when RecyclerView needs another View.
onBindViewHolder()
Question:
“Fill this View with new data.”
Example:
Existing View
↓
User #25
↓
Bind
↓
Now displays User #400
This is called frequently.
It should be fast.
9. Recycling
Imagine:
Visible:
1
2
3
4
5
Scroll.
Now:
2
3
4
5
6
Did Android create another View?
No.
Instead:
View for Item 1
↓
Recycled
↓
Now Displays Item 6
The object survives.
Only the content changes.
10. Recycling Pool
Internally:
RecyclerView
↓
Recycle Pool
↓
Unused Views
When a row disappears:
Visible
↓
Off Screen
↓
Recycle Pool
Later:
Need View
↓
Take From Pool
↓
Bind New Data
Almost no allocations.
Excellent performance.
Part 3 – LayoutManager
RecyclerView doesn’t know where items belong.
LayoutManager decides.
LinearLayoutManager
1
2
3
4
Vertical.
Or horizontal.
GridLayoutManager
1 2
3 4
5 6
Useful for:
- Gallery
- Shopping
- Photos
StaggeredGridLayoutManager
Pinterest style.
■■
■■■■
■
■■■
Different heights.
Notice something interesting.
RecyclerView didn’t change.
Only LayoutManager changed.
That’s great architecture.
Part 4 – ItemDecoration
Suppose you want spacing.
Instead of modifying every item:
RecyclerView offers:
ItemDecoration
Responsibilities:
- Margins
- Dividers
- Decorations
Keeps item layouts simpler.
Part 5 – ItemAnimator
Suppose:
User deletes item.
Without animation:
A
B
C
↓
Delete B
↓
A
C
Looks abrupt.
RecyclerView animates:
A
B
C
↓
Slide
↓
A
C
Smooth.
Part 6 – Why notifyDataSetChanged() Is Bad
Suppose:
One item changed.
Many beginners do:
notifyDataSetChanged()
RecyclerView thinks:
Everything Changed
Rebinds every visible item.
Wasteful.
Instead:
notifyItemChanged(5)
Only one row updates.
Even better:
Use DiffUtil.
Part 7 – DiffUtil
Imagine:
Old List:
A
B
C
D
New List:
A
B
E
D
Without DiffUtil:
Refresh Everything
With DiffUtil:
Only C Changed
↓
Update Row 3
Efficient.
8. ListAdapter
Google built ListAdapter on top of DiffUtil.
Instead of manually calculating differences:
Old List
↓
DiffUtil
↓
Minimal Updates
Automatically.
Most modern apps use ListAdapter rather than implementing RecyclerView.Adapter directly.
Part 8 – Performance Considerations
Avoid Heavy Work in onBindViewHolder()
Bad:
Bind
↓
Download Image
↓
Database Query
Binding should be quick.
Heavy work belongs elsewhere (repositories, image loading libraries, background threads).
Reuse Objects
Avoid creating unnecessary allocations during binding.
Remember:
onBindViewHolder() may execute thousands of times.
Stable IDs
If items have unique identifiers:
RecyclerView can preserve animations more effectively.
Useful for complex updates.
Part 9 – Paging (High Level)
Imagine:
One million products.
Don’t load them all.
Instead:
Load 20
↓
Scroll
↓
Load Next 20
↓
Scroll
↓
Next 20
Jetpack Paging automates this pattern.
We’ll study Paging separately.
Part 10 – LazyColumn (Compose)
RecyclerView doesn’t exist in Compose.
Instead:
LazyColumn {
}
Question:
Does Compose create all rows?
No.
Exactly like RecyclerView:
Visible Items
↓
Compose
↓
Dispose Invisible
↓
Compose New Ones
Same concept.
Different implementation.
Example:
LazyColumn {
items(users) { user ->
UserRow(user)
}
}
Notice:
No Adapter.
No ViewHolder.
No XML.
RecyclerView vs LazyColumn
| RecyclerView | LazyColumn |
|---|---|
| Adapter | items {} |
| ViewHolder | Composable |
| XML Layout | Kotlin UI |
| LayoutManager | Built-in lazy layout |
| DiffUtil | State-driven updates (or keyed items for stability) |
| View Recycling | Composition reuse and lazy item management |
Compose doesn’t expose a ViewHolder, but it still avoids rendering everything at once.
Part 11 – Common Bugs
Bug 1
Store position.
Click
↓
Save Position
↓
Scroll
↓
Wrong Item
Positions change.
Use the current binding position when needed, and prefer stable item IDs over cached positions.
Bug 2
Changing data without notifying Adapter.
RecyclerView still displays old content.
Bug 3
Using notifyDataSetChanged() for every update.
Terrible performance.
Prefer DiffUtil/ListAdapter.
Bug 4
Heavy work inside binding.
Leads to janky scrolling.
Internal Flow
Suppose user scrolls.
Finger Scroll
↓
RecyclerView
↓
Need New Row
↓
Recycle Pool
↓
Reuse ViewHolder
↓
Adapter.onBind()
↓
Display
Notice:
No inflation.
No new objects.
Just reuse.
That’s the secret behind RecyclerView’s efficiency.
Real-World Example
Instagram feed.
Post 1
↓
Post 2
↓
Post 3
Scroll 100 posts.
Instagram does not create 100 View trees.
Instead:
15 Views
↓
Recycled
↓
Bound
↓
Recycled
↓
Bound
Those same view holders display hundreds or thousands of different posts over time.
Best Practices
- Prefer
ListAdapterwithDiffUtilfor most list UIs. - Keep
onBindViewHolder()lightweight. - Use image loading libraries (like Coil, Glide, or Picasso) instead of manual bitmap handling.
- Avoid nested RecyclerViews unless necessary.
- Use stable IDs when appropriate.
- Don’t cache adapter positions.
- In Compose, provide stable keys for list items when items can be inserted, removed, or reordered.
Mental Model
Think of RecyclerView as a hotel.
Rooms (Views)
↓
Guest Leaves
↓
Room Cleaned
↓
New Guest
↓
Same Room
The room isn’t rebuilt.
Only the occupant changes.
That’s exactly what RecyclerView does.
Interview Questions
- Why was RecyclerView introduced?
- Explain the responsibilities of RecyclerView, Adapter, and ViewHolder.
- What is view recycling, and why is it important?
- What’s the difference between
onCreateViewHolder()andonBindViewHolder()? - Why is
notifyDataSetChanged()inefficient? - How does
DiffUtilimprove performance? - What is the role of a
LayoutManager? - Why should
onBindViewHolder()avoid expensive operations? - Compare RecyclerView and LazyColumn.
- How does RecyclerView display thousands of items while creating only a small number of Views?
Module 9 Summary
At this point, you’ve learned:
- Why RecyclerView exists and how it solves large-list rendering.
- The roles of
RecyclerView,Adapter,ViewHolder, andLayoutManager. - How view recycling dramatically reduces memory usage and allocations.
- Why
DiffUtilandListAdapterare preferred over blanket refreshes. - How Compose’s
LazyColumnachieves the same high-level goal with a declarative API.
Next Module: Android Architecture (MVVM, Repository, Clean Architecture & SOLID)
This is arguably the most important module in the entire course.
So far, you’ve learned how Android works.
In Module 10, you’ll learn how professional Android applications are designed.
We’ll cover, in depth:
- Why MVC failed on Android.
- MVP vs MVVM vs MVI.
- Why Google recommends MVVM.
- ViewModel internals and lifecycle.
- Repository pattern.
- Use Cases (Domain layer).
- Clean Architecture.
- Dependency Inversion.
- SOLID principles applied to Android.
- Hilt and Dependency Injection (conceptually first).
- Building a scalable, testable architecture.
This module is where individual Android concepts come together into a maintainable application structure—the same kind you’ll encounter in production codebases and technical interviews.