Singleton Pattern Drawbacks

Medium PriorityAsked in ~50% of mid-level interviews

3 min read

Architecture

ProblemWhy it bites
Hidden dependenciesClass doesn't declare what it uses — you grep to find out
Hard to testCan't swap out for a mock; tests share state
Lifecycle blurLives for the whole process — no clean reset/dispose hook
Global mutable stateTwo screens fighting over the same instance, order-dependent bugs
Concurrency hazardsEasy to write thread-unsafe singletons (less of an issue in Dart, but for async ordering still real)

These compound: each downside makes the next one worse.


Code in action — the same job, two styles

// ❌ Classic singleton — convenient but problematic
class ApiClient {
  ApiClient._();
  static final ApiClient instance = ApiClient._();
  Future<User> fetchUser() async { ... }
}

// Everywhere in your code:
ApiClient.instance.fetchUser();         // dependency hidden, mock impossible
// ✅ Constructor injection + DI (Riverpod here)
class ApiClient {
  ApiClient({required this.baseUrl});
  final String baseUrl;
  Future<User> fetchUser() async { ... }
}

final apiClientProvider = Provider<ApiClient>(
  (ref) => ApiClient(baseUrl: 'https://api.example.com'),
);

// In code
final api = ref.watch(apiClientProvider);
api.fetchUser();

// In tests
ProviderScope(
  overrides: [apiClientProvider.overrideWithValue(MockApiClient())],
  child: MyApp(),
);

You still have "one instance" semantics — but now the dependency is explicit, swappable, and lifecycle-managed.


When a singleton actually fits

SituationSingleton OK?
Pure-function utilities with no state✅ Or just top-level functions
Truly app-wide, immutable config (resolved once)
Plugins that own a native handle (analytics SDK, logger)✅ — but expose via DI for testability
Network client, cache, repository❌ Use DI — you'll want mocks and scoped instances
Anything that holds mutable user state❌ Lift to a state manager
"Just easier than passing it around"❌ Almost always a smell

Common mistakes to avoid

// ❌ Treating a singleton as inevitable
class Cart {
  static final Cart instance = Cart._();
  Cart._();
  final items = <Item>[];
}
// Two test runs share `items` → flaky tests

// ❌ Singletons with cyclical dependencies
// ApiClient uses AuthService, AuthService uses ApiClient → init order matters
// ✅ DI makes the cycle explicit (or breaks it via a separate token store)

// ❌ Resetting singletons via custom global state — leaks
// ✅ ProviderScope (Riverpod) auto-disposes; get_it has registerSingleton + reset()

// ❌ Singletons that capture BuildContext / Navigator
// → tied to a vanished widget tree, crashes when called later

// ❌ Mixing singleton access with DI in the same codebase
// "Some screens use the DI'd version, others use the singleton" → confusion
// Pick one approach and stick to it

Interview follow-ups

  1. Is get_it's registerSingleton "just as bad" as a hand-rolled singleton? It has some of the same drawbacks (global registry, hidden until you grep), but it's a step up: it's typed, replaceable in tests via getIt.registerSingleton<T>(mock), and disposable via getIt.reset(). Better than a static-instance class, but not as scoped or compile-safe as Riverpod.

  2. How do you test code that depends on a true singleton you can't change? You wrap it. Create a thin interface (e.g., Analytics), have one implementation forward to the singleton, inject the interface everywhere. In tests, swap in a fake Analytics. The singleton remains but is now mockable through the seam.

  3. Aren't Provider-style root-scope instances "just singletons"? In effect they're a single instance per ProviderScope — but the lifetime is explicit (disposed when the scope is removed), the dependency is declared (ref.watch(provider)), and overrides for tests are first-class. Same uniqueness, none of the downsides.

  4. When would you prefer a top-level function or class over a singleton? When there's no state. int hash(String s) doesn't need an instance — top-level function. If your "singleton" has no mutable fields and no I/O setup, you don't need a singleton at all.


How helpful was this content?

Please sign in to rate this article.