The Hidden Memory Cost of Scrolling in SwiftUI: A VStack vs LazyVStack vs List Breakdown
When working with SwiftUI, displaying scrollable content is one of the most common tasks. At first glance, it looks simple: just wrap your…
The Hidden Memory Cost of Scrolling in SwiftUI: A VStack vs LazyVStack vs List Breakdown
When working with SwiftUI, displaying scrollable content is one of the most common tasks. At first glance, it looks simple: just wrap your views inside a ScrollView with a VStack. But as soon as you start building real-world apps, you quickly realize that not all scrolling containers are created equal. The way each one handles memory and view lifecycle can make the difference between a smooth experience and a frozen UI.
So the real question is: which one should you actually use? Let’s break it down.
VStack
VStack inside a ScrollView renders all child views eagerly, meaning every single item gets created and loaded into memory the moment the view appears — regardless of whether you can see it on screen or not.
This works perfectly fine for small, static lists. But the moment your dataset grows, you’ll feel it immediately. When tested with 1,000 memory-intensive items, the app froze instantly on launch, and memory spiked to 3.76 GB — because SwiftUI was trying to render everything at once before the UI could even respond.

LazyVStack
LazyVStack was introduced in iOS 14 alongside SwiftUI 2.0 as Apple’s direct answer to a very common problem: VStack’s eager loading behavior was simply not sustainable for dynamic or large lists. The idea was simple — give developers a version of VStack that only does work when it actually needs to.
So what exactly is LazyVStack? Think of it as a smarter version of VStack. It shares the same layout behavior — it stacks views vertically — but the key difference is when those views get created. LazyVStack only creates a view when it’s about to appear on screen. Once an item scrolls off-screen, its view gets discarded from memory. And if you scroll back up, it simply gets recreated from scratch.
This lazy behavior keeps the number of active views low at any given time, which is a big win for both memory and scrolling performance compared to a regular VStack. However, one important thing to keep in mind is that LazyVStack doesn’t reuse views — it destroys and recreates them every time. During testing, memory stayed around the 140–150 MB range throughout the scrolling experience and barely dropped even after scrolling back to the top, which shows that the constant creation and destruction of views does leave a footprint over time.

List
List is not new to the Apple ecosystem. In UIKit, the equivalent was UITableView — one of the most performance-optimized components Apple ever built. UITableView had a powerful trick up its sleeve: cell reuse. Instead of creating a new cell every time a row came into view, it would recycle an off-screen cell and simply update its content with new data. This kept memory low and scrolling buttery smooth, no matter how large your dataset was.
When Apple introduced SwiftUI, they needed something that could bring that same level of efficiency into the new declarative world. That’s exactly what List is. It’s SwiftUI’s equivalent of UITableView, built from the ground up to handle collections of data with the same cell reuse philosophy.
So how does it work in practice? When an item scrolls off-screen, List doesn’t destroy its view. Instead, it recycles that view and rebinds it to new data when a new row comes into view. This means List avoids the constant overhead of creating and destroying views that LazyVStack suffers from. It also comes with a set of built-in features out of the box — selection, swipe actions, and pull-to-refresh — that would otherwise require manual implementation.
In terms of memory, List started slightly heavier than LazyVStack before any scrolling happened. But once scrolling began, the real difference became clear. After scrolling through the entire list and back to the top, List reclaimed memory efficiently, dropping down to around 111 MB — a noticeable improvement over LazyVStack, which held onto around 144 MB in the same scenario. That’s the reuse model doing its job.

Detailed Comparison Table

The numbers tell the story clearly. LazyVStack starts lighter, but once you start scrolling, it holds onto memory and barely recovers. List, on the other hand, peaks at a similar level during scrolling but reclaims memory efficiently when you scroll back up. That’s the reuse model doing its job, and it’s the reason List is the stronger choice for anything performance-sensitive.
Conclusion
Each container serves a specific purpose, and choosing the right one depends on your use case.
- VStack works well for very small, static lists. Once the data starts to grow, it quickly becomes a performance risk.
- LazyVStack is a step up, creating views on demand and performing well with small to medium datasets — especially when you need full control over layout and styling.
- List is the best choice for large or performance-critical collections. Its cell reuse keeps memory usage stable, scrolling smooth, and it provides built-in behaviors that would otherwise require significant manual work.
When in doubt, start with List. If you need layout flexibility that List can’t provide, use LazyVStack. Only reach for VStack when you’re certain the content is small and will stay that way.
메타데이터
- post_id
- bb3f1adaf8ad
- slug
- the-hidden-memory-cost-of-scrolling-in-swiftui-a-vstack-vs-lazyvstack-vs-list-breakdown-bb3f1adaf8ad
- url
- https://medium.com/@ahmedrefatsaid/the-hidden-memory-cost-of-scrolling-in-swiftui-a-vstack-vs-lazyvstack-vs-list-breakdown-bb3f1adaf8ad
- canonical_url
- https://medium.com/@ahmedrefatsaid/the-hidden-memory-cost-of-scrolling-in-swiftui-a-vstack-vs-lazyvstack-vs-list-breakdown-bb3f1adaf8ad
- author_url
- https://medium.com/@ahmedrefatsaid
- status
- ok
- fetched_at
- 2026-06-23 17:05:31