Choosing between LazyVStack, List, and VStack in SwiftUI
Published on: May 8, 2025SwiftUI offers several approaches to building lists of content. You can use a VStack
if your list consists of a bunch of elements that should be placed on top of each other. Or you can use a LazyVStack
if your list is really long. And in other cases, a List
might make more sense.
In this post, I’d like to take a look at each of these components, outline their strengths and weaknesses and hopefully provide you with some insights about how you can decide between these three components that all place content on top of each other.
We’ll start off with a look at VStack
. Then we’ll move on to LazyVStack
and we’ll wrap things up with List
.
Understanding when to use VStack
By far the simplest stack component that we have in SwiftUI is the VStack
. It simply places elements on top of each other:
VStack {
Text("One")
Text("Two")
Text("Three")
}
A VStack works really well when you only have a handful of items, and you want to place these items on top of each other. Even though you’ll typically use a VStack
for a small number of items, but there’s no reason you couldn’t do something like this:
ScrollView {
VStack {
ForEach(models) { model in
HStack {
Text(model.title)
Image(systemName: model.iconName)
}
}
}
}
When there’s only a few items in models
, this will work fine. Whether or not it’s the correct choice… I’d say it’s not.
If your models
list grows to maybe 1000 items, you’ll be putting an equal number of views in your VStack
. It will require a lot of work from SwiftUI to draw all of these elements.
Eventually this is going to lead to performance issues because every single item in your models
is added to the view hierarchy as a view.
Now let's say these views also contain images that must be loaded from the network. SwiftUI is then going to load these images and render them too:
ScrollView {
VStack {
ForEach(models) { model in
HStack {
Text(model.title)
RemoteImage(url: model.imageURL)
}
}
}
}
The RemoteImage
in this case would be a custom view that enables loading images from the network.
When everything is placed in a VStack
like I did in this sample, your scrolling performance will be horrendous.
A VStack
is great for building a vertically stacked view hierarchy. But once your hierarchy starts to look and feel more like a scrollable list… LazyVStack
might be the better choice for you.
Understanding when to use a LazyVStack
The LazyVStack
components is functionally mostly the same as a regular VStack
. The key difference is that a LazyVStack
doesn’t add every view to the view hierarchy immediately.
As your user scrolls down a long list of items, the LazyVStack
will add more and more views to the hierarchy. This means that you’re not paying a huge cost up front, and in the case of our RemoteImage
example from earlier, you’re not loading images that the user might never see.
Swapping a VStack
out for a LazyVStack
is pretty straightforward:
ScrollView {
LazyVStack {
ForEach(models) { model in
HStack {
Text(model.title)
RemoteImage(url: model.imageURL)
}
}
}
}
Our drawing performance should be much better with the LazyVStack
compared to the regular VStack
approach.
In a LazyVStack
, we’re free to use any type of view that we want, and we have full control over how the list ends up looking. We don’t gain any out of the box functionality which can be great if you require a higher level of customization of your list.
Next, let’s see how List
is used to understand how this compares to LazyVStack
.
Understanding when to use List
Where a LazyVStack
provides us maximum control, a List
provides us with useful features right of the box. Depending on where your list is used (for example a sidebar or just as a full screen), List
will look and behave slightly differently.
When you use views like NavigationLink
inside of a list, you gain some small design tweaks to make it clear that this list item navigates to another view.
This is very useful for most cases, but you might not need any of this functionality.
List
also comes with some built-in designs that allow you to easily create something that either looks like the Settings app, or something a bit more like a list of contacts. It’s easy to get started with List
if you don’t require lots of customization.
Just like LazyVStack
, a List
will lazily evaluate its contents which means it’s a good fit for larger sets of data.
A super basic example of using List
in the example that we saw earlier would look like this:
List(models) { model in
HStack {
Text(model.title)
RemoteImage(url: model.imageURL)
}
}
We don’t have to use a ForEach
but we could if we wanted to. This can be useful when you’re using Sections
in your list for example:
List {
Section("General") {
ForEach(model.general) { item in
GeneralItem(item)
}
}
Section("Notifications") {
ForEach(model.notifications) { item in
NotificationItem(item)
}
}
}
When you’re using List
to build something like a settings page, it’s even allowed to skip using a ForEach
altogether and hardcode your child views:
List {
Section("General") {
GeneralItem(model.colorScheme)
GeneralItem(model.showUI)
}
Section("Notifications") {
NotificationItem(model.newsletter)
NotificationItem(model.socials)
NotificationItem(model.iaps)
}
}
The decision between a List
and a LazyVStack
for me usually comes down to whether or not I need or want List
functionality. If I find that I want little to none of List
's features odds are that I’m going to reach for LazyVStack
in a ScrollView
instead.
In Summary
In this post, you learned about VStack
, LazyVStack
and List
. I explained some of the key considerations and performance characteristics for these components, without digging to deeply into solving every use case and possibility. Especially with List
there’s a lot you can do. The key point is that List
is a component that doesn’t always fit what you need from it. In those cases, it’s useful that we have a LazyVStack
.
You learned that both List
and LazyVStack
are optimized for displaying large amounts of views, and that LazyVStack
comes with the biggest amount of flexibility if you’re willing to implement what you need yourself.
You also learned that VStack
is really only useful for smaller amounts of views. I love using it for layout purposes but once I start putting together a list of views I prefer a lazier approach. Especially when i’m dealing with an unknown number of items.