skip to content

Stateless vs Stateful

A StatelessWidget only builds; a StatefulWidget pairs with a State object that outlives each rebuild, and setState marks it dirty for the next frame. Interviewers test when build() actually runs.

part ofFlutteroverview, primer and where to startread it →
on this pageshow

explore

questions

6

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
open as a page

In Flutter, what does calling setState on a State object actually do, and when does build() run afterwards?

level: juniorimportance: must knowfreq 76%

basics

~20 s

setState runs its callback synchronously, then calls markNeedsBuild on the State's element, which marks it dirty and schedules a frame. build() runs during that next frame, not inside the setState call, and several calls before the frame produce one build.

open as a page

In Flutter, why is extracting part of a build method into its own widget class usually better than a helper method returning Widget?

level: middleimportance: should knowfreq 52%

basics

~20 s

An extracted widget gets its own element and BuildContext, so it can own state and rebuild alone, be skipped when its instance is unchanged, and look up ancestors from its own position. A helper method's output is rebuilt with every parent build.

open as a page

In Flutter, when does the framework call createState, and how does one State object outlive the many widget instances built for it?

level: middleimportance: should knowfreq 58%

basics

~20 s

createState runs when a StatefulWidget is inflated into a new element. When the parent later rebuilds with a same-type, same-key widget at that spot, the element keeps its State, repoints State.widget at the new instance and calls build again.

open as a page

Why does a Flutter tip-summary StatefulWidget that copies widget.bill into a field in initState keep showing the old total after the parent passes a new bill, and how do you fix it?

level: seniorimportance: should knowfreq 40%

basics

~20 s

The State outlives each widget instance: initState ran once, so the copied field keeps the first bill while State.widget already holds the new one. Read widget.bill in build, or reconcile the copy in didUpdateWidget when a local copy is genuinely needed.

open as a page

In Flutter, why is a StatefulWidget's build method defined on its State class rather than on the widget itself?

level: middleimportance: nice to knowfreq 20%

basics

~20 s

Closures created in build capture this; on State that is the long-lived object whose widget getter always points at the newest configuration, so callbacks never read a stale widget. It also lets subclasses such as AnimatedWidget keep their State private.

open as a page