skip to content

In Flutter, what is the difference between a StatelessWidget and a StatefulWidget, and when do you actually need the stateful one?

level: juniorimportance: must knowfreq 85%

answer

  1. configuration versus data that changes
  2. createState pairs widget with State
  3. State lives on across rebuilds
  4. setState marks the element dirty
  5. a slider value the widget owns

basics

~20 s

A StatelessWidget builds only from its final fields and inherited data; a StatefulWidget creates a long-lived State object that holds changing values and rebuilds via setState. Choose stateful when the widget itself owns data that changes while it is on screen.

solid answer

~40 s

Both are immutable configuration classes. A `StatelessWidget` has one `build(BuildContext)` that may depend only on its own `final` fields and on inherited widgets it reads through the context; it rebuilds when its parent passes a new instance or an inherited dependency changes. A `StatefulWidget` overrides `createState()`, and the framework keeps the returned `State` attached to the widget's element for as long as it stays at that position in the tree. `build` lives on the `State`, and so do the mutable fields. You need the stateful form when the widget itself owns data that changes during its lifetime, such as a tip slider's current value or a controller it creates, and must call `setState` to say its next `build` will differ. If the value simply arrives from the parent, a `StatelessWidget` is enough.

code

dart · 54 lines
dart
import 'package:flutter/material.dart';

class TipCalculator extends StatefulWidget {
  const TipCalculator({super.key, required this.bill});

  final double bill;

  @override
  State<TipCalculator> createState() => _TipCalculatorState();
}

class _TipCalculatorState extends State<TipCalculator> {
  double _tipPercent = 15;
  double _people = 1;

  @override
  Widget build(BuildContext context) {
    final total = widget.bill * (1 + _tipPercent / 100);
    return Column(
      children: [
        Slider(
          value: _tipPercent,
          max: 30,
          divisions: 30,
          label: '${_tipPercent.round()} %',
          onChanged: (v) => setState(() => _tipPercent = v),
        ),
        Slider(
          value: _people,
          min: 1,
          max: 10,
          divisions: 9,
          label: '${_people.round()} people',
          onChanged: (v) => setState(() => _people = v),
        ),
        TotalLabel(perPerson: total / _people),
      ],
    );
  }
}

class TotalLabel extends StatelessWidget {
  const TotalLabel({super.key, required this.perPerson});

  final double perPerson;

  @override
  Widget build(BuildContext context) {
    return Text(
      'Per person: ${perPerson.toStringAsFixed(2)}',
      style: Theme.of(context).textTheme.headlineMedium,
    );
  }
}

go deeper

for a junior

Recall that both kinds are immutable, that StatefulWidget adds createState and a State holding changing fields, and that setState triggers a rebuild.

for a middle

Explain that the State is attached to the element and survives new widget instances, and use who owns the changing value to decide which kind a widget should be.

for a senior

Show judgement in reviews: flag stateful widgets that own nothing, values copied into State that should be derived, and screens that keep state higher than the change requires.

for a principal

Frame the choice as an ownership boundary: set team conventions for which widgets may hold local state and when state must move to a shared store instead.

## Both kinds are immutable configuration In Flutter a **widget** is a lightweight, immutable description of part of the UI. The base class `Widget` is annotated `@immutable`, so every field on any widget, stateless or stateful, is expected to be `final`. Widgets are cheap and throwaway: a parent's `build` creates fresh instances every time it runs. What persists between builds is the **element**, the framework object that represents a widget's position in the tree. The difference between the two widget kinds is what that element keeps for you. ## StatelessWidget: build from configuration A `StatelessWidget` subclass implements a single method, `Widget build(BuildContext context)`. The framework's own documentation says that method must depend only on: - the widget's own fields, which never change after construction, and - ambient data obtained from the `context`, such as `Theme.of(context)` or `MediaQuery.of(context)`, which registers a dependency on an inherited widget. It runs when the widget is first inserted, when the parent rebuilds and hands it a new instance, and when an inherited widget it depends on changes. It has no `setState` and no place to keep a value between two calls, so it can never change its output on its own initiative. ## StatefulWidget: createState and a long-lived State A `StatefulWidget` subclass is still immutable, but it overrides `State createState()`. When the framework inflates the widget into an element, it calls `createState` once for that element and keeps the returned `State<T>` object for as long as the element lives. The `State` object: 1. holds the mutable fields, such as `_tipPercent`; 2. implements `build`, reading its own fields and the current configuration through the `widget` getter; 3. calls `setState(() { ... })` after changing a field, which marks its element dirty so `build` runs again in the next frame. When the parent rebuilds with a new instance of the same widget class at the same position, the element and its `State` are kept; only `State.widget` is pointed at the new instance. That is why values in the `State` survive while the widget object itself is replaced on every parent build. ## Choosing between them | Situation | Widget kind | |---|---| | Output depends only on constructor arguments and inherited data | `StatelessWidget` | | A value the parent computes and passes in, such as a total to display | `StatelessWidget` | | The widget owns a value the user changes, such as a slider position | `StatefulWidget` | | The widget creates and must dispose an object, such as a `TextEditingController` | `StatefulWidget` | | The widget subscribes to a stream or listenable and must cancel it | `StatefulWidget` | A useful rule: ask **who owns the changing value**. If the answer is the parent or some shared store, the widget that displays it can stay stateless. Only the widget that owns the change needs a `State`. ## A tip calculator, worked through A tip calculator screen shows a bill, a tip-percentage slider, a people slider and a per-person total. The two slider values change while the screen is visible and nobody above the screen needs them, so the screen is a `StatefulWidget` whose `State` holds `_tipPercent` and `_people`. Each slider's `onChanged` calls `setState` with the new value. The total is not stored at all: `build` derives it from `widget.bill` and the two fields every time it runs. A separate label widget that only shows the total could be a `StatelessWidget`, because it receives the number as a constructor argument. ## Common misunderstandings - **Stateless does not mean static.** A stateless widget shows different output whenever its parent passes different arguments or an inherited dependency changes. - **Stateful does not mean the widget is mutable.** The widget object is replaced; the `State` is what persists. - **The `State` is not recreated per build.** It is created once per element and survives every rebuild until the element is discarded. - **Stateful is not a safe default.** An unnecessary `State` adds lifecycle surface without adding anything; pick it only when the widget owns changing data or resources.

  • Can a StatelessWidget ever show different output over time?
    Yes. The framework calls its `build` again when the parent rebuilds and passes a new instance with different field values, and when an inherited widget it read through the context, such as `Theme.of(context)`, changes. What it cannot do is change its own output on its own initiative: it has no `setState` and nowhere to keep a value between builds.
  • Is a StatefulWidget object itself mutable?
    No. `Widget` is annotated `@immutable`, so a `StatefulWidget` subclass should have only `final` fields, and the analyzer warns otherwise. The mutable data lives in the `State`. When the parent rebuilds, a new widget instance is created and the framework hands it to the existing `State` through the `widget` getter.
  • Why not make every widget stateful in case its data changes later?
    An unneeded `State` adds a second class, lifecycle methods to reason about and a place where copied values can go stale, without making anything work that would not work stateless. Changing a stateless widget into a stateful one later is a mechanical refactor, so choose based on who owns the changing data today.

saying these in an interview costs you the question

  • Fields on a StatefulWidget can be reassigned because the widget is stateful
  • A StatelessWidget never rebuilds after it is first built
  • Make every widget stateful in case the data changes later
  • The State object is recreated every time build runs
  • Stateless widgets cannot read Theme.of or MediaQuery.of