← All topics/UIKit & SwiftUI internals

Practical interview questions

Scenario-style prompts with sample answer outlines. Focus is on how you would design and reason in real codebases.

Question 6

SwiftUI state — @State, @Binding, @ObservedObject, @EnvironmentObject

When would you use @State vs @ObservedObject vs @StateObject?

Follow-ups

  • What about @Observable in iOS 17+?

Answer outline

Each wrapper answers: where does this value live, and who creates it?

Principles

  • Who creates the ObservableObject? That view uses @StateObject. Everyone else @ObservedObject.
  • One source of truth: either @State / @StateObject at the owner, or @EnvironmentObject at the root. Don’t duplicate with a second @State copy of the same model.

@State holds value-type state that this view owns. SwiftUI stores it outside the struct so it survives body recomputation.

@State
struct Counter: View {
    @State private var count = 0
    var body: some View {
        Button("Count: \(count)") { count += 1 }
    }
}

Binding<T> is a read/write handle to a value owned elsewhere (usually the parent’s @State). The $ prefix turns a property into a binding when you pass it down.

@Binding
struct Parent: View {
    @State private var isOn = false
    var body: some View { ToggleRow(isOn: $isOn) }
}

struct ToggleRow: View {
    @Binding var isOn: Bool
    var body: some View { Toggle("On", isOn: $isOn) }
}

Use @StateObject when this view creates the ObservableObject. SwiftUI constructs it once for that view’s lifetime (stable identity).

@StateObject
struct Root: View {
    @StateObject private var model = ScreenModel()
    var body: some View { Dashboard(model: model) }
}

Use @ObservedObject when another view already created the object and passed it in. This view only observes it—do not assign a new instance in init or body.

@ObservedObject
struct Dashboard: View {
    @ObservedObject var model: ScreenModel
    var body: some View { Text(model.title) }
}

@EnvironmentObject reads a shared object injected above the subtree with environmentObject(_:) (e.g. Root().environmentObject(SessionStore())). If nothing was injected, the app crashes when the view appears.

@EnvironmentObject
struct Profile: View {
    @EnvironmentObject private var session: SessionStore
    var body: some View { Text(session.userName) }
}

@Observable (Swift 5.9+) is the macro alternative to ObservableObject / @Published. @Bindable lets you form bindings to observable properties on that model.

@Observable / @Bindable
@Observable
final class Cart { var items: [Item] = [] }

struct CartView: View {
    @Bindable var cart: Cart
    var body: some View { List(cart.items) { ... } }
}

Follow-up angles

  • Wrong tool: @ObservedObject var m = ScreenModel() in body → new model every redraw → empty @Published fields.
  • Previews: pass .environmentObject(MockSession()) or your graph is incomplete.