Skip to content

Service locator and anti patterns

Service Locator and Anti-Patterns Deep Dive

Overview

Service Locator can bootstrap dependency access quickly, but it often hides coupling and weakens architectural clarity.

Core Concepts

  • global or scoped registry returns dependencies at runtime
  • call sites hide concrete dependency requirements
  • implicit wiring increases runtime failure risk
  • testing often needs registry mutation/reset behavior

Layer Responsibilities

  • Locator layer:
  • stores and returns object instances/providers
  • Consumers:
  • pull dependencies rather than receive them explicitly
  • Test layer:
  • overrides/replaces registry entries for test isolation

Data Flow

  1. Consumer requests service from locator.
  2. Locator resolves key/provider.
  3. Dependency is returned and used.
  4. Missing/incorrect registration fails at runtime.

Internal Architecture

Why this degrades over time:

  • hidden dependency graph at call sites
  • hard-to-track lifecycle ownership
  • mutation-heavy test setup
  • implicit global state leakage

Controlled usage can be acceptable in tiny apps or transitional migration phases.

Code Examples

object ServiceLocator {
    lateinit var analytics: Analytics
}

class HomeViewModel : ViewModel() {
    fun trackOpen() {
        ServiceLocator.analytics.log("home_open")
    }
}

Common Interview Questions

  • Q: Why is constructor DI generally preferred? A: Frame it around graph ownership: prefer constructor injection, align scopes to lifecycle boundaries, keep contracts explicit, and validate with test replacements.
  • Q: Is Service Locator always an anti-pattern? A: Frame it around graph ownership: prefer constructor injection, align scopes to lifecycle boundaries, keep contracts explicit, and validate with test replacements.
  • Q: How do hidden dependencies hurt testability? A: Answer by defining boundaries and ownership first, then place business rules in the correct layer, and finish with testability and change-resilience tradeoffs.
  • Q: How would you migrate incrementally to DI? A: Frame it around graph ownership: prefer constructor injection, align scopes to lifecycle boundaries, keep contracts explicit, and validate with test replacements.

Production Considerations

  • avoid introducing new locator-based dependencies
  • migrate hotspots first (core services, ViewModels)
  • keep temporary adapters during transition
  • add static checks to block direct global locator usage

Scalability Tradeoffs

  • Pros:
  • fast startup for simple prototypes
  • low initial ceremony
  • Cons:
  • hidden coupling and brittle tests
  • runtime misconfiguration failures
  • poor fit for multi-team codebases

Senior-Level Insights

Senior candidates should present migration strategy, not just anti-pattern labeling. Good answers discuss risk-managed refactors and compatibility bridges while moving toward explicit DI.