What is the difference between Provider, Riverpod, and BLoC?

High PriorityAsked in ~80% of Flutter interviews

3 min read

State Management

Theory — side by side

ProviderRiverpodBLoC
FoundationInheritedWidget + ChangeNotifierIndependent — no widget tree dependencyStream<State> (or Cubit's setter)
API surfacecontext.watch / read / selectref.watch / read / listenBlocBuilder, BlocListener, context.read
Type safetyRuntime errors if you read the wrong typeCompile-time safe (especially with codegen)Compile-time safe
BoilerplateLowLow (lower with riverpod_generator)Higher — events, states, classes
TestingTest the notifier as a classOverride providers in test scopeTest Bloc directly with bloc_test
PatternFlexibleFlexibleStrict: Event → Bloc → State
Learning curveEasyMediumSteeper
Sweet spotSmall / medium appsMost appsLarge apps, big teams, strict discipline

Same feature, three styles

// ─── Provider ──────────────────────────────────────────────
class CounterNotifier extends ChangeNotifier {
  int _c = 0;
  int get count => _c;
  void inc() { _c++; notifyListeners(); }
}

final counter = context.watch<CounterNotifier>();
context.read<CounterNotifier>().inc();
// ─── Riverpod (with codegen) ───────────────────────────────
@riverpod
class Counter extends _$Counter {
  @override
  int build() => 0;
  void inc() => state++;
}

// In a ConsumerWidget
final count = ref.watch(counterProvider);
ref.read(counterProvider.notifier).inc();
// ─── BLoC ──────────────────────────────────────────────────
sealed class CounterEvent {}
class Increment extends CounterEvent {}

class CounterBloc extends Bloc<CounterEvent, int> {
  CounterBloc() : super(0) {
    on<Increment>((e, emit) => emit(state + 1));
  }
}

// Usage
context.read<CounterBloc>().add(Increment());
BlocBuilder<CounterBloc, int>(
  builder: (ctx, count) => Text('$count'),
);

When to choose which

SituationPick
Solo dev / small app / learningProvider
Want compile-time safety and no BuildContext headachesRiverpod
Building a feature-rich app where you'll add many providersRiverpod
Large team, strict architecture, audit trail of state transitionsBLoC
Heavy use of async streams, transactional flowsBLoC (its event/state model fits)
Need to migrate from one to anotherRiverpod has the easiest interop story

There's no "best" — they're tools with different trade-offs.


Common mistakes to avoid

// ❌ Picking the heaviest tool for the smallest job
// 5 screens, 2 toggles, and you stand up BLoC with events + states for each
// ✅ Start with setState or Provider; graduate when complexity demands it

// ❌ Mixing all three in one app for "comparison"
// → cognitive overhead, inconsistent patterns. Pick one.

// ❌ Treating Riverpod like Provider with renamed methods
// You miss the benefits — family, autoDispose, override-for-test, codegen

// ❌ Using BLoC without sealed states
// Switch statements over State become non-exhaustive → bugs
// ✅ Pair BLoC with sealed classes (Q13) for compile-time exhaustiveness

// ❌ Comparing "performance" — all three are fine
// Differences are ergonomic and architectural, not raw speed

Interview follow-ups

  1. Why was Riverpod created if Provider already existed? To fix Provider's pain points: ProviderNotFoundException was a runtime error, dependencies required BuildContext, composition between providers was awkward, and testing required real widget trees. Riverpod moves DI out of the widget tree, making it compile-time safe and easier to test.

  2. What's the BLoC pattern in one sentence? "Send events in, get states out — and the BLoC is a function that maps streams of one to streams of the other." That discipline forces explicit modelling of every state transition.

  3. Cubit vs Bloc — what's the difference? Cubit is Bloc's simpler cousin: instead of dispatching events, you call methods that emit new states directly. Less boilerplate, but you lose the explicit event log. Use Cubit for simple state, Bloc when you want event-driven traceability.

  4. Can you mix state-management approaches in one app? Technically yes — setState for local UI, Provider/Riverpod for cross-screen, a BLoC for one complex flow. Just keep the primary approach consistent across the app, and document why exceptions exist. Mixing freely usually creates more confusion than benefit.


How helpful was this content?

Please sign in to rate this article.