100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Swift

LazyVStack and LazyHStack

Understand how LazyVStack and LazyHStack defer creation of off-screen views inside a ScrollView, enabling efficient custom scrolling layouts.

Lists & ScrollingIntermediate8 min readJul 8, 2026
Analogies

LazyVStack and LazyHStack

A regular VStack or HStack eagerly creates every child view up front, which is fine for a handful of fixed elements but becomes a performance problem inside a ScrollView containing hundreds or thousands of rows—every view exists in memory simultaneously even though only a fraction is ever visible on screen. LazyVStack and LazyHStack solve this by creating child views only as they are about to become visible (plus a small buffer), and releasing them again once they scroll far enough off-screen, mirroring how List and UIKit's UITableView/UICollectionView reuse and recycle cells. They're the building block you reach for when you need List-like scrolling efficiency but List's fixed row-and-separator chrome doesn't fit your custom layout.

🏏

Cricket analogy: A stadium that printed physical scorecards for every possible ball of a 5-day Test in advance would waste enormous resources; instead, like a scoreboard operator who only updates the display for the current over and recycles old panels, LazyVStack only builds rows as they scroll into view.

Where They're Used: Inside ScrollView

Lazy stacks only provide their laziness benefit when embedded in a ScrollView—outside a ScrollView, a lazy stack behaves essentially like its eager counterpart because there's no scrolling viewport to determine what's currently visible. The typical pattern is ScrollView { LazyVStack { ForEach(data) { ... } } }, which gives you List's memory efficiency while letting you fully customize row appearance, spacing, and the absence of default separators/insets that List imposes.

🏏

Cricket analogy: A scoreboard's ball-by-ball ticker only saves effort when it's actually scrolling live through an innings — frozen on a single replay screen, there's no benefit; likewise LazyVStack only pays off inside a ScrollView, and it lets you design a fully custom scorecard layout without List's fixed row template.

swift
struct PhotoFeed: View {
    let photos: [Photo]

    var body: some View {
        ScrollView {
            LazyVStack(spacing: 12) {
                ForEach(photos) { photo in
                    PhotoCard(photo: photo)
                }
            }
            .padding(.horizontal)
        }
    }
}

LazyHStack and Horizontal Scrolling

LazyHStack applies the same lazy-loading idea to horizontal scrolling, commonly used for carousels, story rails, or horizontally paginated content: ScrollView(.horizontal) { LazyHStack { ForEach(...) { ... } } }. Combined with .scrollTargetLayout() and .scrollTargetBehavior(.paging) (iOS 17+), LazyHStack rows can snap to page boundaries for a native-feeling carousel without any manual gesture handling.

🏏

Cricket analogy: A stadium's horizontally scrolling ticker of live match scores from every ground snaps cleanly to each match card, much like LazyHStack combined with .scrollTargetBehavior(.paging) snaps a horizontal carousel to page boundaries automatically.

swift
ScrollView(.horizontal, showsIndicators: false) {
    LazyHStack(spacing: 16) {
        ForEach(stories) { story in
            StoryCircle(story: story)
                .frame(width: 72, height: 72)
        }
    }
    .padding(.horizontal)
}

Think of Lazy stacks as SwiftUI's version of cell reuse: just as UITableView dequeues and recycles cells as you scroll, LazyVStack/LazyHStack construct child views only near the visible viewport and discard the ones far off-screen, keeping memory and layout work proportional to what's on screen rather than the full dataset.

Because lazy stacks don't know a child's size until it's actually instantiated near the viewport, they can't precompute total scroll content size as precisely as List does internally—giving every row a wildly different, dynamically computed height can cause scroll position jumps. Prefer roughly predictable row sizing, or explicit frame/aspect ratios, for a smoother experience with large datasets.

  • LazyVStack and LazyHStack create child views only as they approach the visible scroll viewport, then discard them again when off-screen.
  • Laziness only matters inside a ScrollView—outside one, they behave like eager VStack/HStack.
  • They're used when you need List-like memory efficiency but want full control over row appearance without List's default chrome.
  • LazyHStack combined with .scrollTargetBehavior(.paging) enables native-feeling snapping carousels (iOS 17+).
  • Highly variable, unpredictable row/column sizes can cause scroll position instability with large datasets.
  • Conceptually, lazy stacks are SwiftUI's analog to UIKit's cell reuse in UITableView/UICollectionView.

Practice what you learned

Was this page helpful?

Topics covered

#Swift#IOSWithSwiftUIStudyNotes#MobileDevelopment#LazyVStackAndLazyHStack#LazyVStack#LazyHStack#Where#They#StudyNotes#SkillVeris