If there is one idea that separates beginners who are confused from beginners who are dangerous, it is understanding state. New Flutter developers can copy a counter app and get it working, but the moment they need to decide whether something should be a stateless or a stateful widget, the ground feels shaky. This post is here to make that ground solid. We will look at what state actually means, how stateless and stateful widgets differ, when to reach for each, and the mental model that makes the whole thing finally click.
What “state” actually means
Strip away the jargon and state is just data that can change while your app is running and that affects what the user sees. The number on a counter, whether a checkbox is ticked, the text someone has typed, whether a panel is expanded: all of that is state. Things that never change while the screen is alive, like a fixed title or a logo, are not state. Holding that simple test in your head, does this value change and does the screen care, will answer most of your questions about which widget to use.
Stateless widgets: build once from what you are given
A stateless widget is a widget that has no changing data of its own. You hand it some values when you create it, it describes what to show, and that is the end of the story. If the inputs are the same, the output is always the same. Most of the widgets you build are stateless, and that is a good thing, because they are simple to reason about and cheap to rebuild.
class Greeting extends StatelessWidget {
final String name;
const Greeting(this.name, {super.key});
@override
Widget build(BuildContext context) {
return Text('Hello, $name');
}
}
Notice there is no way for Greeting to change its own name. To show a different greeting, something outside it has to build a new Greeting with a different value. That one-way flow is exactly what makes stateless widgets predictable.
Stateful widgets: able to change themselves over time
A stateful widget is for when a widget needs to hold data that changes and rebuild itself in response. The classic example is a counter that goes up when you tap a button. A stateful widget comes in two parts: the widget itself, and a companion State object that stores the changing data and survives across rebuilds.
class Counter extends StatefulWidget {
const Counter({super.key});
@override
State<Counter> createState() => _CounterState();
}
class _CounterState extends State<Counter> {
int count = 0;
@override
Widget build(BuildContext context) {
return TextButton(
onPressed: () => setState(() => count++),
child: Text('Tapped $count times'),
);
}
}
The key detail is that count lives in the State object, not the widget. When you call setState, Flutter throws away the old description and runs build again with the updated count, but the State object itself sticks around, so the value is remembered. That split is the heart of how stateful widgets work.
How to choose between them
The decision is usually quick once you ask the right question: does this widget need to remember something that changes and redraw itself because of it? If yes, it is stateful. If it just displays whatever it is handed, it is stateless. When you are unsure, start stateless. It is far easier to convert a stateless widget into a stateful one later than to untangle state you added too early, and your editor can even do the conversion for you with a single command.
The mental model that makes it click
Here is the picture that helps most beginners: in Flutter, widgets are cheap, throwaway descriptions, rebuilt constantly. They are not the thing that remembers; they are the snapshot. The State object is the thing that remembers. So when you hear that a widget rebuilds dozens of times a second, do not panic about losing your data. Your count, your text, your toggle all live safely in the State object, and each rebuild simply paints a fresh picture using the current values. Widgets are the frames of the film; the State object is the story being told.
A trap that catches almost everyone
The most common beginner mistake is storing changing data in a stateless widget as a plain variable and wondering why the screen never updates. Because a stateless widget has no State object and no setState, changing that variable does nothing visible; Flutter has no reason to rebuild. If you find yourself wanting to change a value and see the result, that is your signal: this widget needs to be stateful, or the value needs to live in a parent that is. Recognizing that signal early will save you hours of confusion.
When state needs to live higher up
Sometimes a value changes and two different widgets both care about it. A classic case is a checkbox in one place and a Save button somewhere else that should only light up when the box is ticked. If each widget tried to own that state, they would quickly fall out of sync. The Flutter answer is to lift the state up: move the changing value into the nearest widget that sits above both of them, then pass the value down to each child as an input, along with a callback the child can call to request a change. The shared parent becomes the single source of truth, and the children stay refreshingly simple. This pattern feels strange the first time, because your instinct is to keep data close to where it is used. But lifting state up is one of the most important habits in Flutter, and grasping it early is what lets you grow from single-screen toys into real apps where many parts of the interface react to the same piece of information.
Do stateful widgets hurt performance?
A worry that stops some beginners from using stateful widgets is the fear that rebuilding is expensive. In practice, Flutter is built from the ground up to rebuild widgets quickly, and a rebuild is not the same as repainting every pixel or restarting the app. When setState runs, Flutter compares the new description against the old one and changes only what actually differs on screen, so the cost is usually tiny. Where beginners do run into slowdowns is by making a single stateful widget enormous, so that one small change forces a huge subtree to rebuild. The fix is not to avoid state; it is to keep your stateful widgets small and focused, so that when something changes, only the part that truly needs to update gets rebuilt. Reach for state freely, and simply keep the widget that owns it as small as the job allows.
Putting it together
Stateless versus stateful is not really about memorizing two class shapes. It is about a single question you will ask thousands of times: is this thing fixed, or does it change and matter to the screen? Answer that honestly and the right widget almost picks itself. If you want to go deeper on the rebuild cycle, the official Flutter guide to adding interactivity is excellent, and if a setState ever seems to do nothing, our roundup of common Flutter mistakes beginners make covers exactly why. Master state, and Flutter stops feeling like magic and starts feeling like something you control.
