StatefulWidget vs StatelessWidget: The Flutter Lifecycle Explained

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.