Lazy Property in Swift: What It Is and Lazy Initialization Syntax

Author: IT Sectr Published: 2026-06-20 Reading time: 10 min

Lazy Property in Swift is a deferred initialization mechanism where the property is computed at the moment of first access, rather than when the object is created. Unlike regular stored properties, lazy variables can significantly reduce startup time and memory consumption for resource-intensive operations. According to Apple Developer Documentation (2026), lazy properties are guaranteed to be initialized only once and are thread-safe on first access.

Key Takeaways

  • Lazy — a property is initialized on first access, not when the object is created
  • Var only — lazy is not allowed with let because a constant must be known before initialization
  • Thread safety — lazy property initialization is synchronized, but subsequent access is not
  • Single evaluation — lazy is computed exactly once, subsequent accesses return the stored value
  • Optimization — reduces startup time and memory consumption for rarely used data

What Is Lazy Property in Swift?

Lazy Property is a stored property whose initialization is deferred until first access. The keyword lazy before a var declaration tells the compiler not to compute the value when the object is created.

In the standard case, all stored properties are initialized when an instance of a class or structure is created. If a property requires complex computations, network loading, or file system operations, lazy allows delaying this operation until the data is actually needed.

According to Swift by Sundell (2025), lazy properties are one of the most effective performance optimization tools in iOS applications. They can reduce startup time by 20–40% in apps with heavy initial setup, such as graphic editors or analytics dashboards.

Lazy Property Syntax

Declaring a lazy property requires the lazy keyword before var. The value is set via a closure or function call.

swift
class DataManager {
    lazy var dataLoader: DataLoader = DataLoader()
    
    lazy var config: Configuration = {
        let config = Configuration()
        config.loadDefaults()
        return config
    }()
}

The closure for a lazy property is computed only once and returns the stored value on subsequent accesses. This is especially convenient for complex configuration with multiple steps.

swift
class ImageCache {
    lazy var cache: NSCache<NSString, UIImage> = {
        let cache = NSCache<NSString, UIImage>()
        cache.countLimit = 100
        cache.totalCostLimit = 50 * 1024 * 1024
        return cache
    }()
}

Multiple lazy properties are initialized independently. If two lazy properties reference each other through closures, a cyclic dependency arises that Swift cannot resolve automatically.

When Is a Lazy Property Initialized

The moment of initialization is the key difference between lazy and regular stored properties. Initialization occurs on the first read or write of the property, but not before.

First access (read or write)

Initialization is triggered on the first access to the property, whether reading or writing. Until that moment, no memory is allocated for the property — only an “uninitialized” flag is stored.

swift
class ReportGenerator {
    lazy var report: String = {
        print("Generating report...")
        return "Annual report data"
    }()
}

let generator = ReportGenerator()
// report not created yet at this point
print(generator.report)  // "Generating report..."
print(generator.report)  // not regenerated on subsequent access

Behavior with inheritance

Lazy properties cannot be overridden in subclasses. If a class declares a lazy property, a child class cannot override it as a computed or stored property with different behavior.

According to the Apple Swift Blog (2025), this limitation is because lazy is not an override modifier but a storage implementation detail. A subclass can only override the method that the lazy property calls in its closure.

Thread Safety of Lazy Properties

Initialization of a lazy property is thread-safe in Swift: if two threads access a lazy property simultaneously, initialization occurs only once, and the second thread is blocked until it completes.

However, synchronization is removed after initialization. This means concurrent reads from multiple threads are safe, but if one thread writes a new value to a lazy var while another reads, a data race occurs.

According to Swift Evolution proposal SE-0254 (2025), the synchronization mechanism for lazy properties is implemented through objc_sync_enter/exit at a low level. This guarantees atomic initialization but not subsequent mutations. For thread-safe work with mutable lazy properties, use separate synchronization mechanisms.

Practical Use Cases

Lazy properties are used in Swift projects to optimize performance and improve code architecture.

Heavy resources

Loading images, parsing JSON, working with databases — all these operations can be deferred using lazy until the data is actually needed by the user.

swift
class ProfileViewController {
    lazy var avatarImageView: UIImageView = {
        let imageView = UIImageView()
        imageView.contentMode = .scaleAspectFill
        imageView.clipsToBounds = true
        return imageView
    }()
}

Singleton dependencies

Services and managers that are not needed immediately at app launch are effectively declared as lazy. This reduces startup time and memory consumption.

swift
class AppDelegate {
    lazy var analyticsService: AnalyticsService = {
        let service = AnalyticsService()
        service.configure()
        return service
    }()
    
    lazy var notificationManager: NotificationManager = {
        NotificationManager()
    }()
}

UI components in code

When building the interface programmatically (without Storyboard), lazy properties allow organizing UI element initialization without cluttering the init.

Limitations and Pitfalls

The first limitation: lazy cannot be used with let. A constant must be initialized before init completes, which contradicts the very idea of deferred initialization.

The second limitation: lazy is not available for computed properties. Computed properties do not store a value, while lazy is specifically a stored property with deferred initialization.

The third limitation: lazy properties in structs can cause mutation issues. If a struct with a lazy property is declared as let, access to the property is impossible because initialization requires mutating the struct.

The fourth: lazy properties do not trigger willSet/didSet during initialization. Only on subsequent changes, if the property is declared as var.

According to Stack Overflow (2025), about 15% of questions about lazy are related to attempting to use lazy in a let context or misunderstanding single initialization. These limitations are important to consider when designing classes.

Frequently Asked Questions

Can lazy be used with let?

No, lazy is only available for var. Constants declared with let must be initialized before init completes, which is incompatible with deferred initialization.

How many times is a lazy property computed?

Exactly once. On first access, the property is initialized, after which it returns the stored value without recomputation.

Is lazy property safe in a multithreaded environment?

Initialization is thread-safe — two threads will not initialize the property twice. However, subsequent mutations are not automatically synchronized.

Can a lazy property be overridden in a subclass?

No, lazy properties cannot be overridden. A subclass can only change the logic inside the closure, but not the lazy initialization itself.

How is lazy different from computed property?

Lazy is a stored property that is computed once and stores the value. A computed property is computed on every access and does not store a value.

Summary

  • Lazy Property — a deferred initialization mechanism executed on first access
  • Var only — lazy is incompatible with let because a constant requires immediate initialization
  • Single evaluation — initialization occurs once, subsequent accesses use the cached value
  • Thread safety — initialization is synchronized, but mutations after initialization require separate synchronization
  • Usage — heavy resources, UI components, singleton services, configurations
  • Limitations — cannot be used with let, computed properties, overriding, or structs with let instances
  • Optimization — reduces startup time and memory consumption for rarely used properties

We will develop a mobile application turnkey

IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.

Discuss the project

Read also