Explain the concept of "lifting state up"

Medium PriorityAsked in ~55% of Flutter interviews

2 min read

State Management

Lifting state up is the simplest form of state management and the foundation for everything bigger. When two siblings each track their own copy of "selected tab" or "form value", they drift out of sync. The fix is structural: hoist the state up.

BeforeAfter
Each child is StatefulWidget with its own stateParent is StatefulWidget, owns the state
Siblings can't see each other's dataSiblings get data + callbacks via constructor
State divergesSingle source of truth

Once state lives in the parent, anything that needs to read or change it gets passed (value, onChanged).


Code in action — coordinating two toggles

// Parent owns the state
class ToggleGroup extends StatefulWidget {
  const ToggleGroup({super.key});
  @override
  State<ToggleGroup> createState() => _ToggleGroupState();
}

class _ToggleGroupState extends State<ToggleGroup> {
  bool _a = false;
  bool _b = false;

  @override
  Widget build(BuildContext context) => Column(children: [
    ToggleItem(label: 'A', value: _a, onChanged: (v) => setState(() => _a = v)),
    ToggleItem(label: 'B', value: _b, onChanged: (v) => setState(() => _b = v)),
    Text('A=$_a, B=$_b'),                           // both visible & in sync
  ]);
}

// Child is now stateless — value comes in, change goes out
class ToggleItem extends StatelessWidget {
  const ToggleItem({
    super.key,
    required this.label,
    required this.value,
    required this.onChanged,
  });

  final String label;
  final bool value;
  final ValueChanged<bool> onChanged;

  @override
  Widget build(BuildContext context) => SwitchListTile(
    title: Text(label),
    value: value,
    onChanged: onChanged,
  );
}

This is exactly the same pattern as React's controlled components: value flows down, events flow up.


When to lift

SituationAction
Two siblings show or change the same dataLift to common parent
A child wants to "tell" the parent something happenedPass a callback (onTap, onChanged)
State needs to outlive the child being unmounted/rebuiltLift to a parent that stays mounted
Several screens need the same dataLift it higher — eventually out of widgets and into a store (Provider/Riverpod/BLoC)

Lifting is the first step; when "the parent" becomes "the entire app", you've outgrown it — graduate to a state-management package.


Common mistakes to avoid

// ❌ Two siblings each owning their own state of the same thing
class TabBarA extends StatefulWidget { ... }    // tracks selectedIndex
class TabContent extends StatefulWidget { ... } // tracks selectedIndex too
// → they drift apart

// ✅ Parent owns selectedIndex, passes it down

// ❌ Lifting too far — pushing state to the root for one screen-local toggle
// Adds noise; lift only as far as the common ancestor that needs it.

// ❌ Passing 10 props to thread state down 5 levels deep ("prop drilling")
// Signal that it's time for a state-management lib or InheritedWidget

// ❌ Mutating a child's value in the parent without notifying back
// Always pair `value` with `onChanged` so the parent owns the truth

// ❌ Lifting state that doesn't actually need to be shared
// Local state should stay local — over-lifting causes parent rebuild storms

Interview follow-ups

  1. What's the difference between lifting state up and using a global state manager? Lifting puts state in the nearest common ancestor — still part of the widget tree. A global store (Provider/Riverpod/BLoC) lifts it out of the tree entirely, so any widget can read it without prop drilling. Use lifting until that drilling gets painful.

  2. Why pair value and onChanged instead of letting the child own the value? So the parent stays the single source of truth. The child becomes a pure function of its props, which makes it predictable, testable, and easy to reuse.

  3. When does lifting state become a code smell? When you're threading the same data through several intermediate widgets that don't use it themselves (prop drilling), or when the "lifted" StatefulWidget owns logic that belongs in a service/repository. Both are signals to introduce a state manager.

  4. How does lifting state relate to the InheritedWidget pattern? Lifting solves the ownership problem; InheritedWidget solves the access problem. Once state is lifted, you can either thread it down via constructors (simple) or expose it via an InheritedWidget so descendants can grab it without prop drilling.


How helpful was this content?

Please sign in to rate this article.