What is the difference between StatelessWidget and StatefulWidget?
3 min read
Flutter Basics
Default to StatelessWidget — it's a pure function of its constructor params. Promote to StatefulWidget only when something genuinely needs to change within the widget over time.
StatelessWidget | StatefulWidget | |
|---|---|---|
| Internal state | ❌ | ✅ (in a separate State object) |
| Lifecycle hooks | Just build() | initState, didChangeDependencies, didUpdateWidget, dispose… |
| Triggers own rebuild | ❌ Only when parent rebuilds | ✅ Via setState() |
| Memory cost | Very cheap | Slightly heavier (extra State object) |
Why two classes? Widgets are immutable and recreated on every build. The State is the persistent bit that survives across rebuilds — Flutter keeps it alive in the Element tree.
Code in action
// Stateless — purely a function of constructor args
class Greeting extends StatelessWidget {
final String name;
const Greeting({required this.name, super.key});
@override
Widget build(BuildContext context) => Text('Hello, $name!');
}
// Stateful — owns mutable state, calls setState to rebuild
class Counter extends StatefulWidget {
const Counter({super.key});
@override
State<Counter> createState() => _CounterState();
}
class _CounterState extends State<Counter> {
int _count = 0;
void _inc() => setState(() => _count++);
@override
Widget build(BuildContext context) => Column(
children: [
Text('$_count'),
ElevatedButton(onPressed: _inc, child: const Text('+')),
],
);
}
When to pick which
| You need… | Use |
|---|---|
| Render derived from props/props-only | StatelessWidget |
| Track counters, toggles, scroll position, form input | StatefulWidget |
Hold an AnimationController, TextEditingController, StreamSubscription | StatefulWidget (you need dispose) |
| Wrap and re-style other widgets | StatelessWidget |
| State lives elsewhere (Provider/Riverpod/BLoC) | StatelessWidget + watch the store |
Rule: if no field needs to mutate and survive across rebuilds, stay stateless.
Common mistakes to avoid
// ❌ StatefulWidget when nothing actually changes
class Title extends StatefulWidget { ... }
class _TitleState extends State<Title> {
@override
Widget build(BuildContext context) => Text(widget.text); // pure render!
}
// ✅ Make it stateless
// ❌ Putting mutable fields directly on a StatelessWidget
class Counter extends StatelessWidget {
int count = 0; // ❌ widgets are recreated on rebuild → reset every time
}
// ❌ Mutating state without setState — UI won't refresh
void _inc() { _count++; } // ❌ Flutter doesn't know to rebuild
// ❌ Skipping const constructors on stateless widgets
return Padding(padding: EdgeInsets.all(8), child: ...); // rebuilt every parent build
// ✅ const Padding(padding: EdgeInsets.all(8), child: ...)
// ❌ Forgetting dispose() on controllers in StatefulWidget
late final _ctrl = TextEditingController();
// dispose() override missing → memory leak
Interview follow-ups
-
Why are widgets immutable, and what's the role of the
Stateobject? Immutable widgets mean rebuilds are cheap and predictable — Flutter recreates the widget tree on every build. TheStateobject lives in the Element tree and survives those rebuilds, which is where any mutable, long-lived data lives. -
What's the lifecycle order when a StatefulWidget mounts?
createState()→initState()→didChangeDependencies()→build(). On parent rebuild with new config:didUpdateWidget()thenbuild(). On removal:deactivate()→dispose(). -
Why prefer
StatelessWidgeteven when convenient to make everything stateful? Smaller memory footprint, nodisposebookkeeping,constconstructors enable canonicalisation, and the code is easier to reason about. Stateful is a tool, not a default. -
What's the cost of
setState? It marks the State's element as dirty, so Flutter rebuilds that subtree on the next frame. It's cheap if yourbuild()is cheap and the subtree is small — push state down to the smallest widget that needs it.
How helpful was this content?
Please sign in to rate this article.