Every widget in Flutter is either a StatelessWidget or a StatefulWidget. The choice isn’t about complexity or how “important” a widget is — it’s about one specific question: does this widget need to hold onto a value that can change while it’s on screen?
StatelessWidget: Built Once From Its Inputs
A StatelessWidget is fully described by the parameters passed into its constructor. Given the same parameters, its build() method always produces the same UI. It can still change what’s on screen — but only by being rebuilt with different parameters from its parent, never on its own.
class Greeting extends StatelessWidget {
final String name;
const Greeting({super.key, required this.name});
@override
Widget build(BuildContext context) {
return Text('Hello, $name!');
}
}
Greeting has no way to change its own name — it just displays whatever it’s given. If the greeting needs to change, that has to happen by the parent rebuilding Greeting with a new value.
StatefulWidget: Holds Its Own Mutable Value
A StatefulWidget is split into two classes: the widget itself (immutable, like a StatelessWidget) and a separate State object that Flutter keeps alive across rebuilds and that’s allowed to hold mutable fields.
class LikeButton extends StatefulWidget {
const LikeButton({super.key});
@override
State<LikeButton> createState() => _LikeButtonState();
}
class _LikeButtonState extends State<LikeButton> {
bool _liked = false;
@override
Widget build(BuildContext context) {
return IconButton(
icon: Icon(_liked ? Icons.favorite : Icons.favorite_border),
onPressed: () => setState(() => _liked = !_liked),
);
}
}
The key detail: _liked lives on the State object, not the LikeButton widget. Flutter can throw away and recreate the LikeButton instance on every rebuild — it’s cheap and immutable — while preserving the same _LikeButtonState instance and everything stored on it.
Why the Split Exists
Separating the widget (cheap, disposable, rebuilt constantly) from the state (expensive to lose, kept alive) is a deliberate performance design. Widgets in Flutter are meant to be lightweight configuration objects rebuilt very frequently; if every widget could hold arbitrary mutable state directly, Flutter would have no clean way to decide what to preserve across a rebuild.
The Decision Rule
Default to StatelessWidget. Only reach for StatefulWidget when the widget genuinely needs to hold a value that changes over its own lifetime, independent of its parent rebuilding it — an animation’s current value, a text field’s controller, a toggle’s current position. If the “changing” value actually comes from outside the widget (a database, an API response, a value shared across screens), it usually belongs in a state management solution like Provider or Riverpod rather than local State.