← All topics/Architecture & design patterns

Practical interview questions

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

Question 3

Dependency injection — constructors, containers, and test seams

The codebase uses a global ServiceLocator.shared singleton for networking and analytics. What problems does that cause, and how would you migrate toward something testable?

Follow-ups

  • What about SwiftUI’s environment vs manual injection?

Answer outline

A global ServiceLocator.shared hides what a type really needs — code may look simple, but secretly depend on networking, analytics, persistence, or feature flags. The problems compound:

  1. 1.Hidden requirements — call sites cannot see what a type actually needs; the signature lies.
  2. 2.Test brittleness — tests must mutate shared global state, causing leakage and order-dependent failures.
  3. 3.Tight coupling — every caller is bound to one concrete implementation with no seam for substitution.
  4. 4.Concurrency hazards — mutable shared singletons are prone to data races under concurrent tests or multiple scenes.

The better default is dependency injection: pass dependencies in through an initializer so the type's requirements are explicit and easy to swap.

Migrate gradually: define protocols for key services, inject them in new or touched code, and push remaining service-locator calls up to the app's composition root.

In SwiftUI, use Environment for dependencies shared by a view subtree. Prefer initializer injection when a dependency should be obvious and replaceable in tests.

Principles

  • Avoid hiding dependencies behind globals.
  • Prefer initializer injection for clarity and testability.
  • Use protocols when tests or multiple implementations need substitution.
  • Move service lookup upward toward the app composition layer.
  • Treat SwiftUI Environment as scoped injection, not a replacement singleton.
Constructor injection
final class ProfileViewModel {
    private let profileAPI: ProfileAPI
    private let analytics: AnalyticsTracking

    init(profileAPI: ProfileAPI, analytics: AnalyticsTracking) {
        self.profileAPI = profileAPI
        self.analytics = analytics
    }
}