DevAtlas

Learn

  • Dashboard
  • Learning Paths
  • All Categories
  • Topics

Practice

  • Interview Questions
  • Flashcards
  • Quizzes
  • Study Plans

Personal

  • Bookmarks
  • Notes
  • Progress
  • PDF Library
  • Settings
  • About
  1. Topics
  2. StatelessWidget vs StatefulWidget
0%
Flutter

StatelessWidget vs StatefulWidget

Every Flutter UI is a tree of widgets. Learn the two fundamental building blocks — the immutable StatelessWidget and the mutable-state StatefulWidget — and when to reach for each.

Beginner22 min study10 min read
Export PDF

Learning objectives

  • Explain what makes a StatelessWidget immutable and when it rebuilds.
  • Describe how a StatefulWidget stores mutable state in a persistent State object and refreshes the UI with setState().
  • Decide which widget type to use for a given piece of UI, preferring stateless where possible.
  • Explain why Flutter separates the widget (config) from its State (persistent data).

Simple explanation

Think of a movie poster versus a live scoreboard at a stadium. A poster is printed once and never changes — if the film changes, you print a brand-new poster. A scoreboard keeps the same frame bolted to the wall, but the numbers on it change all game long.

A StatelessWidget is the poster: it draws itself purely from the information handed to it and never changes on its own. A StatefulWidget is the scoreboard: the widget itself is a light frame, but it is paired with a separate object that *holds numbers that change over time* and can ask Flutter to redraw when they do.

Technical explanation

In Flutter, everything on screen is a widget, and widgets are cheap, immutable descriptions of what the UI should look like right now. There are two ways to build your own.

A StatelessWidget has no internal mutable state. All of its data comes in through its constructor (its final fields). It exposes a single build() method that returns a subtree. Because it holds nothing that can change, Flutter only rebuilds it when its inputs change — a parent passes new constructor arguments — or when an inherited widget it depends on (via BuildContext) changes. Given the same inputs, it always produces the same output.

A StatefulWidget is really two classes working together:

  1. The widget class itself — still immutable, still just final configuration — whose job is to create state.
  2. A separate State object, returned from createState(), that *persists across rebuilds* and is allowed to hold mutable fields.

You mutate that state by calling setState(), which updates the fields and marks the element dirty so Flutter schedules a rebuild. The build() method lives on the State, so it reads the current mutable values every time it runs. The State object is also where the lifecycle lives: initState() runs once when the state is inserted, and dispose() runs once when it is removed for good — the right places to start and stop timers, animations, and stream subscriptions.

Why it matters

Choosing correctly keeps your UI both correct and fast. A stateless widget is easier to reason about, cheaper to rebuild, and trivially testable because it is a pure function of its inputs. Reaching for StatefulWidget only when you genuinely need to remember something between frames — a toggle, a text field's contents, an animation's progress — keeps most of your tree simple. Misjudging this leads to two classic problems: UIs that silently fail to update, and UIs that leak resources or rebuild far more than they should.

How it works

When Flutter builds the UI it walks the widget tree and, for each widget, creates a matching Element that stays in place across frames:

  1. For a StatelessWidget, the element simply calls build() again whenever the widget is replaced or a dependency changes.
  2. For a StatefulWidget, the element also owns the long-lived State object. The widget can be thrown away and recreated on every frame, but the State survives — so the data it holds survives too.
  3. Calling setState() doesn't repaint immediately; it flags the element as dirty. On the next frame Flutter calls build() again, diffs the new subtree against the old one, and updates only what changed.

That is why the widget can be immutable while the UI still feels dynamic: the *changing part* lives in the persistent State, not in the disposable widget.

Real-world analogy

Imagine a hotel room (the State) and the little printed card slipped into the door slot that says which guest is staying (the widget). Guests come and go — the card is reprinted constantly — but the room itself, with all the belongings inside, stays put. Swapping the card is cheap; it doesn't empty the room. In the same way, Flutter can rebuild the lightweight StatefulWidget as often as it likes without throwing away the State (and its data) sitting behind it.

Important terminology

  • Immutable — once constructed, a widget's fields never change; to show something different, Flutter builds a new widget instance.
  • State object — the persistent companion of a StatefulWidget that holds mutable data and survives rebuilds.
  • setState() — the call that updates state fields and tells Flutter to schedule a rebuild; changing a field without it will not update the UI.
  • Lifecycle (initState/dispose) — hooks on the State for one-time setup and teardown, e.g. starting and cancelling a subscription.

Key definitions

StatelessWidget
An immutable widget with no internal mutable state; it renders purely from its constructor inputs and inherited data.
StatefulWidget
A widget paired with a persistent State object that can hold mutable data and trigger rebuilds over time.
State object
The long-lived companion returned by createState() that persists across rebuilds and hosts the build() method and lifecycle.
setState()
A method on State that updates mutable fields and marks the widget dirty so Flutter schedules a rebuild.

Must memorize

Must memorize

  • StatelessWidget = immutable; it rebuilds only when its inputs or an inherited dependency change.
  • StatefulWidget keeps its data in a separate State object that survives rebuilds.
  • Change state only inside setState(); mutating a field without it will not repaint the UI.
  • initState() and dispose() live on the State — use them to set up and tear down timers, animations, and subscriptions.

Real-world example

A profile header vs. a like button

A profile header that just shows a name and avatar passed in from its parent is a perfect StatelessWidget — it never changes on its own. A like button that toggles filled/unfilled and updates a count when tapped needs a StatefulWidget, because it must remember its own toggled state and call setState() to redraw.

A countdown timer screen

A screen that counts down each second uses a StatefulWidget: it starts a periodic timer in initState(), calls setState() every tick to update the remaining seconds, and cancels the timer in dispose() so it does not leak or fire after the screen is gone.

Code example

greeting_card.dart
import 'package:flutter/material.dart';

class GreetingCard extends StatelessWidget {
  const GreetingCard({super.key, required this.name});

  // Immutable configuration — comes from the constructor.
  final String name;

  @override
  Widget build(BuildContext context) {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Text('Hello, $name!'),
      ),
    );
  }
}

A StatelessWidget: pure output from its final inputs.

counter.dart
import 'package:flutter/material.dart';

class Counter extends StatefulWidget {
  const Counter({super.key});

  @override
  State<Counter> createState() => _CounterState();
}

class _CounterState extends State<Counter> {
  int _count = 0; // Mutable state — lives on the State, survives rebuilds.

  void _increment() {
    // Update the field AND ask Flutter to rebuild.
    setState(() {
      _count++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        Text('Count: $_count'),
        ElevatedButton(
          onPressed: _increment,
          child: const Text('Increment'),
        ),
      ],
    );
  }
}

A StatefulWidget: mutable count updated through setState().

ticker.dart
import 'dart:async';
import 'package:flutter/material.dart';

class Ticker extends StatefulWidget {
  const Ticker({super.key});

  @override
  State<Ticker> createState() => _TickerState();
}

class _TickerState extends State<Ticker> {
  int _seconds = 0;
  Timer? _timer;

  @override
  void initState() {
    super.initState();
    _timer = Timer.periodic(const Duration(seconds: 1), (_) {
      setState(() => _seconds++);
    });
  }

  @override
  void dispose() {
    _timer?.cancel(); // Prevent leaks: stop the timer when removed.
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Text('Elapsed: $_seconds s');
  }
}

A StatefulWidget using the lifecycle: start a timer in initState, clean up in dispose.

Common mistakes

Mistake

Reaching for StatefulWidget by default, even when the widget only displays data passed in from its parent.

Do this instead

Prefer StatelessWidget whenever a widget has no data that changes on its own. It is simpler, cheaper to rebuild, and easier to test.

Mistake

Mutating a State field directly (e.g. `count++;`) and expecting the UI to update.

Do this instead

Wrap the change in setState(() { count++; }) so Flutter marks the widget dirty and schedules a rebuild.

Interview questions

Quiz

Question 1 of 30 ✓
When does a StatelessWidget rebuild?
Select an answer to continue.

Flashcards

Card 1 of 3

Show answer · Space

Summary

A StatelessWidget is an immutable description of UI that renders purely from its inputs and only rebuilds when those inputs or an inherited dependency change. A StatefulWidget pairs a light, immutable widget with a persistent State object that holds mutable data, exposes the build() method and lifecycle (initState/dispose), and refreshes the UI via setState(). Prefer stateless widgets by default, and choose stateful only when a widget must remember something that changes over time — input, animations, timers, or subscriptions.

References

  • Flutter Docs — StatelessWidget class
  • Flutter Docs — StatefulWidget class

Related topics

Flutter Widget LifecycleBuildContext in Flutter
Previous
Flutter Widget Lifecycle
Next
BuildContext in Flutter

On this page

  • Learning objectives
  • Simple explanation
  • Technical explanation
  • Why it matters
  • How it works
  • Real-world analogy
  • Important terminology
  • Key definitions
  • Must memorize
  • Real-world example
  • Code example
  • Common mistakes
  • Interview questions
  • Quiz
  • Flashcards
  • Summary