Stored vs computed vs lazy vs property observers (willSet/didSet)?

1.6.1 · L1 · Interview

Predict first

In what order do the lines print?

struct S {
    lazy var x: Int = { print("init x"); return 1 }()
}

var s = S()
print("made")
_ = s.x
_ = s.x
  • init x, made
  • made, init x
  • made, init x, init x

Mental model. Stored is a drawer. Computed is a vending machine — you get a value on demand, nothing is kept. lazy is a drawer built on first use.

Stored occupies memory. Computed has get/set and no storage of its own. lazy var is initialized once on first access, must be a var, and is not thread-safe by default.

willSet/didSet fire on a stored property when it is written. The precision that matters: they do not fire for a property initialized inside its own class's init, but they do fire when a subclass assigns a superclass property after super.init(). You cannot have a custom setter and observers on the same property.

lazy runs its initializer on first access, exactly once (on a single thread).

Common traps

  • “didSet never fires in an initializer.” It does not fire for the property's own initialization, but a subclass assigning a superclass property after super.init() does trigger it.
  • “lazy is thread-safe because it runs once.” It runs once per access path under race — two threads can both initialize it. See Q41.

They might follow up with

  • Can a let be lazy? No — lazy requires storage that mutates on first access, so it must be var.
  • Can a struct have a lazy property? Yes, but any method that touches it becomes mutating.

Related questions

  • 1.6.2 Property observers vs overriding a setter — which do you use?
  • 1.6.5 Are property wrappers thread-safe? Is lazy?

Property observers vs overriding a setter — which do you use? →