Architecture & patterns cheat sheet

The 9 Architecture & patterns interview answers in 30 seconds each, plus quick facts. Printable, free.

One page per topic: print it or save it as PDF from your browser's print menu. Practise these questions

MVC, MVVM, VIPER and TCA compared

All of them separate UI from logic; they differ in how many layers there are and how strict the data flow is. MVC is simple but controllers grow big. MVVM moves presentation logic into a testable view model and fits SwiftUI well. VIPER splits every screen into five roles, which suits large teams at a high boilerplate cost. TCA enforces one-way state, actions and reducers with strong testing support, but you depend on a library. Choose by team size, testability needs and how much ceremony you can afford.

MVVM done right

The model holds data and business rules. The view model turns model data into display-ready state, handles user intents and calls injected services, without importing UIKit or holding views. The view renders that state and forwards actions. In SwiftUI, make the view model @Observable and @MainActor, and inject its dependencies so it can be tested without UI.

The delegate pattern

A delegate is an object that another object calls through a protocol to ask questions or report events, like UITableViewDelegate. It suits one-to-one communication with several related callbacks. Declare the protocol AnyObject and the property weak to avoid retain cycles. For a single callback a closure is lighter; for many listeners, use observation.

The observer pattern

Observers subscribe to a subject and are told when it changes, so one change reaches many listeners without the subject knowing them. In Swift today that is usually an @Observable model that SwiftUI tracks automatically, withObservationTracking or Observations outside SwiftUI, an AsyncSequence, or Combine publishers. NotificationCenter and KVO remain for system events and Objective-C APIs.

Factory and builder patterns

A factory centralises object creation, so callers ask for 'a profile screen for this id' without wiring dependencies themselves. A builder assembles a complex object step by step. In Swift, builders are often replaced by structs with default parameters, or result builders for DSLs like ViewBuilder, while factories stay useful for wiring screens and swapping implementations in tests.

Dependency injection

Dependency injection means a type receives what it needs instead of creating or fetching it globally. Prefer initializer injection with protocol types, defaulting to real implementations at the composition root. It makes dependencies visible, lets tests pass stubs, and lets previews use fake data. SwiftUI's environment is injection for view trees.

Strategy and command patterns

Strategy puts interchangeable algorithms behind one protocol, so the caller picks one at runtime, such as different export formats or pricing rules. Command wraps an action as a value with everything needed to run it, so it can be queued, logged, retried or undone. In Swift, strategies are often protocols or closures, and commands are often enums of actions.

The coordinator pattern

A coordinator owns navigation for a flow: it creates screens, injects their dependencies and decides what comes next, while screens just report events through callbacks. This keeps view controllers reusable and flows testable. In SwiftUI the same idea is a router or navigation model that owns a NavigationPath and maps routes to views.

Modularizing a growing app

Measure first, then split the app into Swift packages or frameworks along feature and layer boundaries: core modules (design system, networking, persistence) at the bottom, feature modules above them, and the app target as a thin composition root. Break feature-to-feature dependencies with small interface modules, give each feature a demo app, and enforce the dependency rules in CI.

Quick facts

Composition root
The one place, usually at app launch, where the object graph is built and dependencies are wired.
Singleton
One shared instance. Acceptable for truly global resources, but inject it so tests can replace it.
MV in SwiftUI
Some SwiftUI apps skip separate view models and put @Observable feature models next to views; the testing argument still applies.