When should you use setState()?

High PriorityAsked in ~80% of Flutter interviews

2 min read

State Management

setState(fn) does two things:

  1. Runs fn() synchronously — this is where you mutate fields.
  2. Marks the State's element as dirty so Flutter rebuilds it on the next frame.

It's not magical — the widget rebuilds because the element was marked dirty, not because Flutter "watched" your variable.


Code in action

class _CounterState extends State<Counter> {
  int _count = 0;
 
  void _inc() {
    setState(() => _count++);             // mutate INSIDE the callback
  }
 
  Future<void> _loadInitial() async {
    final v = await api.fetchCount();
    if (!mounted) return;                  // ⚠️ guard after async
    setState(() => _count = v);
  }
 
  @override
  Widget build(BuildContext context) => Column(children: [
    Text('$_count'),
    ElevatedButton(onPressed: _inc, child: const Text('+')),
  ]);
}

When to reach for setState vs something bigger

SituationUse
Toggle, counter, expanded/collapsedsetState
Form fields, validation flagssetState
Local animation flag, hover statesetState
State needed by sibling widgetsLift state up (Q35), or use Provider/Riverpod
State needed by many screens (auth, cart)Provider / Riverpod / BLoC
Async loaded data, streamsProvider's FutureProvider/StreamProvider, or FutureBuilder / StreamBuilder
Persisted state (settings)A repository + state-management layer

Rule of thumb: if you'd have to pass it through more than two widgets, lift it out of setState.


Common mistakes to avoid

// ❌ Mutating outside setState
_count++;
setState(() {});                  // Works, but making the code less clear
// ✅ setState(() => _count++);
 
// ❌ Calling setState in build()
@override
Widget build(BuildContext context) {
  setState(() { ... });                   // 💥 infinite rebuild loop
}
 
// ❌ Calling setState after the widget unmounted
fetch().then((d) => setState(() => _d = d));   // crash if user navigated away
// ✅
if (!mounted) return;
setState(() => _d = d);
 
// ❌ Doing heavy work inside setState
setState(() {
  _bigList = expensiveSort(items);        // sorts on the UI thread, blocks frame
});
// ✅ Compute first, then setState with the result
 
// ❌ Calling setState in dispose()
// ✅ Don't — the widget is going away
 
// ❌ Using setState for app-wide auth state
// One login flow grows into many screens needing the user → switch to a store

Interview follow-ups

  1. What does if (mounted) do, and why is it needed? mounted is a bool on State that's false once the widget has been disposed. Async work might complete after the user navigates away — calling setState then throws. The guard avoids it.

  2. What's the cost of setState? It rebuilds the State's build() and the subtree it returns. Push your StatefulWidget down to the smallest area that actually changes — that way each setState only rebuilds a tiny piece.

  3. When does setState become a code smell? When the same data must be passed through several widgets, when async loading state proliferates copy-pasted patterns, or when business logic creeps into your State classes. That's the signal to introduce a state manager.


How helpful was this content?

Please sign in to rate this article.