"Should this be stateless or stateful?" is a decision you make dozens of times in a Flutter app, and getting the rule of thumb right keeps your code simple. The short version: a StatelessWidget draws itself from the data it is given and never changes, while a StatefulWidget can hold data that changes over time and rebuild itself. This tutorial covers what each one is, the separate State object and its lifecycle, when a widget genuinely needs mutable state, how to convert between them, and why you should reach for stateless first — with runnable code.

The Complete Flutter Guide: Build Android, iOS and Web apps
Go from scratch to building industry-standard apps with Riverpod, Firebase, animations, REST APIs, and more.
Enrol nowBoth are widgets, both have a build method, and both are cheap. The whole question is whether the widget needs to change itself once it is on screen.
We'll build a small example of each so the difference is concrete rather than abstract.
If you'd rather watch the two types compared side by side in a live app, the channel walks through it.
StatelessWidget: immutable by design
A StatelessWidget has no internal state. You pass it values through its constructor, it builds its UI from those values, and that is the end of the story — it cannot change itself afterwards. It has a single build method and its fields are final.
class Greeting extends StatelessWidget {
const Greeting({super.key, required this.name});
final String name;
@override
Widget build(BuildContext context) {
return Text('Hello, $name');
}
}
If the parent rebuilds Greeting with a different name, you get a brand-new widget with the new value — the old one is simply discarded. That immutability is a feature: a stateless widget is trivial to reason about because its output depends only on its inputs.
Note the const constructor and the final field. Both are conventional for stateless widgets and both earn their keep: final enforces that nothing inside the widget changes, and a const constructor lets Flutter reuse instances across rebuilds and skip rebuilding the subtree entirely. The vast majority of the widgets you write — labels, icons, buttons, cards, layout wrappers — are stateless, and that is exactly how it should be.
StatefulWidget and the State object
A StatefulWidget comes in two parts: the widget class, which is still immutable, and a separate State class that holds the mutable data. The widget creates its State via createState, and the State is where build and your changing fields live.
class Counter extends StatefulWidget {
const Counter({super.key});
@override
State<Counter> createState() => _CounterState();
}
class _CounterState extends State<Counter> {
int _count = 0;
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () => setState(() => _count++),
child: Text('Tapped $_count times'),
);
}
}
The State object persists across rebuilds, which is why _count survives. Calling setState mutates the field and asks Flutter to rebuild. The State also exposes lifecycle hooks like initState and dispose for setting up and tearing down resources.
This two-class split feels like boilerplate at first, but it exists for a reason explored below. The widget remains a cheap, immutable description that the framework can recreate at will, while the State is the durable companion that holds anything that must survive a rebuild. From inside the State you can always reach the widget's own constructor fields through widget — for example widget.title — so configuration passed by the parent and mutable data owned by the state live cleanly in their own places.
When you actually need state
Reach for a StatefulWidget only when the widget must hold data that changes over its lifetime and the framework should react to it. The common cases are a toggle or checkbox, a text field's contents, an animation controller, a counter, or anything driven by a timer or stream. You also need it when you want initState/dispose hooks.
// Stateful: a switch that flips between on and off.
bool _enabled = false;
Switch(
value: _enabled,
onChanged: (v) => setState(() => _enabled = v),
);
If the widget's appearance is fully determined by the values passed in — a label, a styled card, a layout wrapper — it does not need state. Reading from a controller or a provider in build does not make a widget stateful either; the changing data lives elsewhere. Every widget gets its handle to the tree through the BuildContext regardless of which type it is.

Build a clean widget architecture from day one
The Complete Flutter Guide shows when to use stateless, stateful, and dedicated state management in real apps.
Enrol nowConvert a stateless widget to stateful
You will often start stateless and discover later that a widget needs to change itself. In most IDEs you can place the cursor on the class and use the "Convert to StatefulWidget" assist, but it is worth knowing what it produces so you can do it by hand. The widget class keeps its constructor and fields; a new State class takes over the build method.
// Before: stateless.
class Toggle extends StatelessWidget {
const Toggle({super.key});
@override
Widget build(BuildContext context) => const Icon(Icons.star_border);
}
// After: stateful, now able to flip the icon.
class Toggle extends StatefulWidget {
const Toggle({super.key});
@override
State<Toggle> createState() => _ToggleState();
}
class _ToggleState extends State<Toggle> {
bool _on = false;
@override
Widget build(BuildContext context) {
return IconButton(
icon: Icon(_on ? Icons.star : Icons.star_border),
onPressed: () => setState(() => _on = !_on),
);
}
}
Notice the mutable field _on moved into the State class, not the widget — the widget itself stays immutable. Access the widget's own constructor fields from the State through widget.fieldName.
Converting the other direction — stateful back to stateless — is just as worthwhile when a widget turns out not to need its state after all, perhaps because you lifted that state up to a parent or out to a provider. Collapsing it back to a single class removes a whole layer of indirection and a State object you would otherwise have to maintain. Treat the widget type as a decision you can revisit, not a permanent commitment made the moment you create the file.
Prefer stateless where you can
When in doubt, start stateless. It is simpler, has no lifecycle to manage, and there is nothing to leak. Promote to stateful only when a real need appears — a value that must change in place. And when state needs to be shared across several widgets or screens rather than living inside one, that is the signal to move it out into a state management solution instead of nesting deeper stateful widgets. Keeping most of your tree stateless makes the parts that do change far easier to find.
Common mistakes
- Making everything stateful "just in case". Default to stateless; reach for stateful only when a value must change in place.
- Putting mutable fields on the widget. Mutable data belongs in the
Stateclass, not the immutableStatefulWidget. - Confusing reading data with owning it. Reading from a controller or provider in
builddoes not require aStatefulWidget. - Forgetting dispose. Controllers, timers, and subscriptions created in a
Statemust be released indispose. - Solving shared state with nested stateful widgets. When state spans screens, lift it into proper state management instead.
Frequently asked questions
What is the difference between StatelessWidget and StatefulWidget?
A StatelessWidget is immutable and builds once from its inputs, so it has only a build method. A StatefulWidget pairs an immutable widget with a separate State object that holds mutable data and can call setState to rebuild. Use stateless for input-driven UI, stateful when the widget must change itself.
When should I use a StatefulWidget?
Use one when the widget must hold and change data over its lifetime — a toggle, a form input, an animation, a counter, or anything driven by a timer or stream — or when you need initState and dispose. If appearance depends only on the values passed in, a StatelessWidget is enough.
Why is the State object separate from the widget?
Widgets are immutable and recreated on every rebuild, so they cannot hold changing data. The State object lives separately and persists across rebuilds, which is what lets your data survive while the widget configuration is replaced — keeping scroll positions and input values intact.
Is StatelessWidget faster than StatefulWidget?
The difference is negligible; both are very cheap. A StatelessWidget is marginally lighter with no State to allocate, but do not choose for performance. Choose based on whether mutable state is genuinely needed, and prefer stateless when it is not because it is simpler.
Further reads
Keep going with the tutorials that pair with this guide:
- Flutter Development Guide 2026 — the full Flutter hub.
- Flutter setState Explained — how rebuilds happen.
- Flutter Widget Lifecycle — initState to dispose.
- Flutter BuildContext Explained — the handle every widget gets.
- Flutter State Management in 2026 — Provider, Riverpod, BLoC.
Sources: Flutter documentation — StatelessWidget, StatefulWidget, and State (api.flutter.dev). Verified against current stable Flutter.