← Back to blog
Flutter Internals · Part 7 of 7
September 26, 202626 min read

100 Questions to Master Flutter Internals

FlutterDartInternals

100 Questions to Master Flutter Internals

This is Part 7 — the finale of the Flutter Internals series. The previous six parts explained the engine; this is where you prove you understand it deeply enough to explain under interview pressure.

How to use this bank:

  • 100 questions, grouped by topic, tagged [Basic], [Medium], [Advanced] and marked (Theory) or (Coding).
  • Each has a Hint and a separate Solution. Try cold first, peek at the hint if stuck, only then check the solution.
  • For (Coding) questions, sketch the widget/render code yourself. dartpad.dev runs Flutter snippets.
  • After the 100, there are 10 coding mini-exercises with full solutions, ending in a capstone that traces one tap through every layer.

If you can explain the why on all 100 without hints, you understand Flutter internals at a senior level. Let's go.


Section A — The three trees in depth (Q1–17)

Q1. [Basic] (Theory) Name the three trees and each one's job.

Hint

Config / instance / pixels. See Part 1.

Solution

Widget = immutable config (recreated each build); Element = persistent instance (holds state, is the BuildContext, reconciles); RenderObject = layout + paint.

Q2. [Basic] (Theory) Where does a StatefulWidget's state actually live?

Hint

Not the widget.

Solution

In the State held by the StatefulElement (Element tree). That's why state survives widget rebuilds.

Q3. [Basic] (Theory) Why is rebuilding the whole widget tree cheap?

Hint

Elements/render objects persist.

Solution

Widgets are cheap immutable config (often const, canonicalized); the expensive Elements and RenderObjects are kept and updated in place via reconciliation, so only changed properties are touched.

Q4. [Medium] (Theory) State the canUpdate rule.

Hint

Two fields.

Solution

An Element is reused for a new widget only if runtimeType and key both match. Match → update in place (state kept); mismatch → dispose + rebuild (state lost).

Q5. [Medium] (Coding) Reused or replaced? Text('a')Container().

Hint

runtimeType.

Solution

Replaced — different runtimeType, so canUpdate is false; the old Element is disposed.

Q6. [Medium] (Coding) Reused or replaced? Box(key: ValueKey(1))Box(key: ValueKey(2)).

Hint

Same type, diff key.

Solution

Replaced — same type but different key fails canUpdate; state is lost.

Q7. [Medium] (Theory) Walk through updateChild's four cases.

Hint

null/null/canUpdate/else.

Solution

New widget null → deactivate old child. Old child null → inflate/mount new. canUpdate true → reuse (child.update). canUpdate false → replace (deactivate old, inflate new).

Q8. [Basic] (Theory) What are the two main Element flavors?

Hint

Compose vs render.

Solution

ComponentElement (composes other widgets, e.g. StatelessElement/StatefulElement) and RenderObjectElement (creates/manages a RenderObject).

Q9. [Medium] (Theory) What is the BuildOwner and what does it do per frame?

Hint

Dirty list.

Solution

It tracks dirty elements (from markNeedsBuild) and each frame rebuilds them in tree order (buildScope), running build/updateChild to reconcile, after which changed render objects lay out and paint.

Q10. [Medium] (Coding) Trace setState(() => x++) to a repaint.

Hint

markNeedsBuild → BuildOwner → canUpdate → render.

Solution

Mutate fields → markNeedsBuild() on the Element → BuildOwner dirty list → next frame build() + updateChild/canUpdate reconcile → changed RenderObjects markNeedsPaint/Layout → pipeline paints.

Q11. [Advanced] (Theory) Why doesn't changing a Text's string create a new RenderObject?

Hint

Reused element → update.

Solution

canUpdate matches (same type+key), so the Element is reused and updateRenderObject mutates the existing render object's text + marks needs-paint. New render objects only appear on element replacement.

Q12. [Basic] (Theory) Which tree is the BuildContext?

Hint

Middle.

Solution

The ElementElement implements BuildContext.

Q13. [Medium] (Theory) Why separate Widget from Element at all?

Hint

Disposable vs persistent.

Solution

So widgets can be recreated cheaply every build while a persistent Element holds state and decides what actually changed — keeping expensive render objects alive across rebuilds.

Q14. [Advanced] (Coding) A const child in a rebuilt parent — what happens to it?

Hint

Identical → skip.

Solution

A const widget is canonicalized to one instance; on rebuild the new child is identical() to the old, so canUpdate reuses the Element and Flutter can skip that subtree entirely. (const)

Q15. [Advanced] (Theory) How does markNeedsBuild connect setState to the framework?

Hint

State Mgmt Part 1.

Solution

setState calls markNeedsBuild() on the State's Element, enrolling it in the BuildOwner's dirty list so it rebuilds next frame. It's the bridge from a field change to reconciliation. (State Mgmt Part 1)

Q16. [Medium] (Coding) Same type, same key, new data: Text('a', key: k)Text('b', key: k). Outcome?

Hint

canUpdate true.

Solution

Reused — same type+key, so the Element/render object are kept and just updated to show 'b'.

Q17. [Advanced] (Theory) Why is reconciliation O(n) over the children, not O(n²)?

Hint

Position-paired (+keys).

Solution

Children are matched by position (with keys helping match by identity within a list), so each old/new child pair is compared once via canUpdate — a single linear walk, not all-pairs matching.


Section B — Keys (Q18–34)

Q18. [Basic] (Theory) What does a key control?

Hint

The key half of canUpdate.

Solution

The key in canUpdate — it makes reconciliation match an Element by identity instead of position, deciding which Element/state is reused.

Q19. [Basic] (Theory) Do most widgets need keys?

Hint

No.

Solution

No — only same-type, stateful widgets in a collection that can reorder or change membership generally need them.

Q20. [Medium] (Theory) Explain the "reorder resets state" bug.

Hint

Matched by slot.

Solution

Without keys, widgets match existing Elements by position, so State stays glued to the slot. After a reorder/removal the wrong rows keep state. Keys make matching follow item identity.

Q21. [Medium] (Coding) Choose a key: items have a unique id int.

Hint

Value.

Solution

ValueKey(item.id).

Q22. [Medium] (Coding) Choose a key: items have no id but stable object identity.

Hint

Object.

Solution

ObjectKey(item).

Q23. [Medium] (Coding) You want a widget to fully reset every build. Which key, and the risk?

Hint

Unique.

Solution

UniqueKey() — a new identity each build forces a fresh Element. Risk: in a list it destroys reuse and resets all state every frame.

Q24. [Medium] (Theory) LocalKey vs GlobalKey scope?

Hint

Siblings vs app.

Solution

LocalKey is unique among siblings; GlobalKey is unique across the whole app (and grants state/context access + re-parenting).

Q25. [Medium] (Coding) How do you call validate() on a Form from elsewhere?

Hint

GlobalKey currentState.

Solution
final k = GlobalKey<FormState>();
Form(key: k, ...);
k.currentState!.validate();

Q26. [Advanced] (Theory) How does a GlobalKey move a widget across the tree without losing state?

Hint

Re-parent the element.

Solution

It identifies an Element globally, so when the keyed widget reappears elsewhere, Flutter re-parents the existing Element (with its State/RenderObject) instead of rebuilding — preserving state.

Q27. [Advanced] (Theory) Why is GlobalKey more expensive than LocalKey?

Hint

Global registry.

Solution

The framework maintains a global registry mapping each GlobalKey to its Element (for lookups/re-parenting), and it must stay unique app-wide — more bookkeeping than a sibling-scoped LocalKey.

Q28. [Medium] (Coding) Where must a list-item key be placed?

Hint

Sibling level.

Solution

On the item widget that's a direct child of the collection's children (the sibling level the list reconciles) — not buried deeper inside the item.

Q29. [Medium] (Theory) When do you NOT need keys?

Hint

Stable / stateless / distinct types.

Solution

When the structure is stable and never reorders, the widgets are stateless, or siblings are different types (so runtimeType already distinguishes them).

Q30. [Advanced] (Coding) Two TextFields swap positions and the text seems stuck. Fix and explain.

Hint

Keys make them follow.

Solution

Add distinct keys (ValueKey('a'), ValueKey('b')). Without them both are TextField at positions 0/1, so swapping reuses Elements by slot. Keys make Flutter match by identity so the fields truly swap.

Q31. [Basic] (Theory) Name the three LocalKey subtypes.

Hint

Value/Object/Unique.

Solution

ValueKey, ObjectKey, UniqueKey.

Q32. [Advanced] (Theory) Why does UniqueKey() in a ListView.builder hurt performance?

Hint

New identity each build.

Solution

Every build yields new keys → canUpdate always fails → every item's Element/state rebuilt from scratch each frame, destroying reuse and resetting state.

Q33. [Medium] (Coding) Will adding ValueKeys to a never-reordered stateless list change behavior?

Hint

Mostly harmless.

Solution

Functionally it's harmless (and can aid diffing), but it's unnecessary — with stable order and no state there's nothing for keys to fix.

Q34. [Advanced] (Theory) What happens if the same GlobalKey is used in two places in the tree simultaneously?

Hint

Must be unique.

Solution

It throws — a GlobalKey must identify one Element at a time. Duplicate simultaneous use violates uniqueness and crashes.


Section C — BuildContext (Q35–50)

Q35. [Basic] (Theory) What is a BuildContext concretely?

Hint

The element.

Solution

The Element for a widget — a handle to its location in the Element tree.

Q36. [Basic] (Theory) Which direction does of(context) search?

Hint

Up.

Solution

Upward from context to the nearest matching ancestor (usually an InheritedWidget).

Q37. [Medium] (Coding) Fix "Scaffold.of() ... does not contain a Scaffold" inside a page returning a Scaffold.

Hint

Builder.

Solution

Wrap the child in a Builder so its context is below the Scaffold:

Scaffold(body: Builder(builder: (context) =>
  ...ScaffoldMessenger.of(context)...));

Q38. [Medium] (Theory) Why does the page's own context fail to find the Scaffold it returns?

Hint

Above it.

Solution

The page's context sits above the Scaffold, and of searches upward — it can't find a Scaffold that's below the starting context.

Q39. [Medium] (Coding) Guard a context used after await.

Hint

mounted.

Solution
await something();
if (!context.mounted) return;
Navigator.of(context).pop();

Q40. [Medium] (Theory) What does context.mounted tell you?

Hint

Still in tree?

Solution

Whether the Element backing the context is still mounted in the tree. Check it after async gaps before using the context.

Q41. [Advanced] (Theory) Why is reading inherited values in initState problematic?

Hint

Doesn't react.

Solution

The dependency isn't set up to react to later changes and some lookups aren't valid that early; the value can go stale. Use didChangeDependencies or build.

Q42. [Basic] (Theory) What widget introduces a context one level deeper?

Hint

Builder.

Solution

Builder — its builder callback receives a context below the Builder.

Q43. [Medium] (Theory) Why can two contexts on the same screen return different Themes?

Hint

Different start points.

Solution

Theme.of searches upward from each context; if they start at different locations (one below an inner Theme override), their nearest Theme ancestor differs.

Q44. [Advanced] (Theory) Why is storing a context for later dangerous?

Hint

May deactivate.

Solution

A context is valid only while its Element is mounted/in place; after rebuilds/navigation/disposal it can be deactivated, so using a stored one throws "deactivated widget's ancestor." Capture concrete values synchronously instead.

Q45. [Medium] (Coding) State.mounted vs context.mounted — which to use where?

Hint

State vs bare context.

Solution

In a State, use mounted (State.mounted). With a bare BuildContext (stateless callback), use context.mounted. Both mean "element still in the tree."

Q46. [Medium] (Theory) What two things can a context do that justify passing it everywhere?

Hint

Lookups + anchor.

Solution

Find ancestors (of(context), inherited dependencies) and act as the anchor for Navigator/Theme/MediaQuery/overlays — all relative to its tree location.

Q47. [Advanced] (Theory) Why does dependOnInheritedWidgetOfExactType need a context, mechanically?

Hint

Start node + register.

Solution

The context (Element) is the starting node for the upward type lookup and the node registered as a dependent, so the framework knows which element to rebuild when the inherited widget changes. (State Mgmt Part 2)

Q48. [Basic] (Coding) Which lint flags using a context after an await?

Hint

use_build_context...

Solution

use_build_context_synchronously.

Q49. [Medium] (Theory) Can a context see its descendants?

Hint

No (directly).

Solution

Not directly via of-style lookups — those go upward. (You can reach a render object via findRenderObject, but normal inherited lookups only see ancestors.)

Q50. [Advanced] (Theory) Summarize the one rule that explains all context bugs.

Hint

Location + upward.

Solution

A context is a location in the tree and lookups search upward from it; using a context from the wrong location (too high, deactivated, or another subtree) makes lookups fail or find the wrong ancestor.


Section D — Widget lifecycle (Q51–67)

Q51. [Basic] (Theory) Which hooks fire exactly once?

Hint

Start/end.

Solution

initState (start) and dispose (end). didChangeDependencies is once-then-on-change; build/didUpdateWidget fire many times.

Q52. [Basic] (Coding) Put in order: build, initState, createState, didChangeDependencies.

Hint

Birth → prep → deps → perform.

Solution

createStateinitStatedidChangeDependenciesbuild.

Q53. [Medium] (Theory) What belongs in initState?

Hint

One-time setup.

Solution

One-time setup — create controllers/subscriptions, initial values. Call super.initState() first; don't do change-tracking inherited lookups or setState here.

Q54. [Medium] (Theory) didChangeDependencies vs didUpdateWidget — when does each fire?

Hint

Inherited vs own props.

Solution

didChangeDependencies when an inherited dependency (via of(context)) changes; didUpdateWidget(old) when the parent rebuilds with new widget config (same State reused).

Q55. [Medium] (Coding) A subscription set up in initState from widget.stream goes stale when the parent passes a new stream. Fix.

Hint

didUpdateWidget.

Solution
@override
void didUpdateWidget(W old) {
  super.didUpdateWidget(old);
  if (old.stream != widget.stream) { _sub.cancel(); _sub = widget.stream.listen(_on); }
}

Q56. [Medium] (Theory) Why doesn't initState re-run when a parent passes new props?

Hint

Same State reused.

Solution

canUpdate keeps the same State and just hands it a new widget — the Element isn't recreated, so initState doesn't run again. Reconcile prop-dependent setup in didUpdateWidget.

Q57. [Basic] (Coding) What's the super call order for initState and dispose?

Hint

First / last.

Solution

super.initState() first, super.dispose() last.

Q58. [Medium] (Theory) What must dispose do?

Hint

Release resources.

Solution

Release everything acquired in setup — dispose() controllers, cancel() subscriptions/timers, remove listeners — to avoid leaks. super.dispose() last.

Q59. [Advanced] (Theory) Why does forgetting dispose cause a memory leak?

Hint

Reachable State.

Solution

Undisposed controllers/subscriptions keep the State (and its subtree) reachable, so the GC can't reclaim it — a retained-reference leak. (GC)

Q60. [Medium] (Coding) Where to build a DateFormat derived from inherited Locale so it updates?

Hint

didChangeDependencies.

Solution

In didChangeDependencies — it re-runs when the inherited Locale changes.

Q61. [Medium] (Theory) What is deactivate and how does it differ from dispose?

Hint

Might return.

Solution

deactivate = removed from the tree but might be reinserted (e.g. GlobalKey re-parent). dispose = gone for good; release resources. deactivate is rarely overridden.

Q62. [Basic] (Theory) Does StatelessWidget have lifecycle hooks?

Hint

Just build.

Solution

No — only build. The lifecycle hooks belong to State.

Q63. [Medium] (Theory) After dispose, what is mounted and why does it matter?

Hint

false → guard async.

Solution

mounted is false. Async callbacks must check it before setState/using context, or they throw "setState after dispose."

Q64. [Advanced] (Coding) A TickerCard subscribes per widget.symbol. Write the hooks that keep it correct and leak-free.

Hint

initState + didUpdateWidget + dispose.

Solution
void initState() { super.initState(); _sub = stream(widget.symbol).listen(_on); }
void didUpdateWidget(o) { super.didUpdateWidget(o);
  if (o.symbol != widget.symbol) { _sub.cancel(); _sub = stream(widget.symbol).listen(_on); } }
void dispose() { _sub.cancel(); super.dispose(); }

Q65. [Medium] (Theory) Which hook fires on hot reload only?

Hint

reassemble.

Solution

reassemble() — dev-only (hot reload); never in production.

Q66. [Advanced] (Theory) Why is build required to be side-effect-free?

Hint

Runs often, unpredictably.

Solution

It can run many times per second (rebuilds, dependency changes) and must be safe to re-run. Side-effects (starting requests, mutating external state, creating controllers) there cause duplication, leaks, and bugs — those belong in initState/didUpdateWidget/callbacks.

Q67. [Advanced] (Theory) Tell the difference between "inherited changed" and "props changed" and the hook for each.

Hint

deps vs widget.

Solution

Inherited value (read via of(context)) changed → didChangeDependencies. Your own widget.* props changed (parent rebuild) → didUpdateWidget. They're commonly confused; the source of the change tells you the hook.


Section E — Slivers & CustomScrollView (Q68–83)

Q68. [Basic] (Theory) What is a sliver?

Hint

Scrollable segment.

Solution

A scrollable segment of a viewport laid out with a scroll-aware protocol — a piece of the "belt" that a CustomScrollView arranges with others. (Part 5)

Q69. [Basic] (Theory) How does CustomScrollView differ from ListView?

Hint

Many slivers.

Solution

ListView is a viewport with one sliver; CustomScrollView places multiple slivers in one viewport so they scroll together.

Q70. [Medium] (Theory) How does sliver layout differ from box layout?

Hint

SliverConstraints → SliverGeometry.

Solution

Boxes: BoxConstraintsSize (2D). Slivers: SliverConstraintsSliverGeometry (1D, scroll-aware — scroll extent, paintExtent/visible amount, where the next sliver starts).

Q71. [Medium] (Coding) Put a single Card banner between two slivers. Which widget?

Hint

Adapter.

Solution

SliverToBoxAdapter(child: Card(...)).

Q72. [Medium] (Coding) Keep a SliverList lazy for 100k rows.

Hint

Builder delegate.

Solution
SliverList(delegate: SliverChildBuilderDelegate(
  (c, i) => Tile(data[i]), childCount: data.length));

Q73. [Medium] (Theory) Why do sliver lists scale to huge datasets?

Hint

Lazy build.

Solution

SliverChildBuilderDelegate builds children lazily (only visible ones + a small cache) and disposes off-screen ones, keeping live Elements/RenderObjects roughly constant.

Q74. [Advanced] (Theory) Explain a collapsing SliverAppBar in protocol terms.

Hint

paintExtent shrinks.

Solution

As scrollOffset grows, its paintExtent (visible size) shrinks from expandedHeight toward the toolbar height. pinned clamps paintExtent at the toolbar height; floating lets it grow again on upward scroll.

Q75. [Medium] (Coding) Add padding around a sliver. Which widget, and why not Padding?

Hint

SliverPadding.

Solution

SliverPaddingPadding is a box widget and can't sit in slivers: or wrap a sliver.

Q76. [Basic] (Theory) What does pinned: true do on a SliverAppBar?

Hint

Stays at top.

Solution

Keeps the toolbar visible (pinned) at the top when scrolled, instead of scrolling fully away.

Q77. [Medium] (Theory) Why is nesting scrollables to combine regions an anti-pattern?

Hint

Unbounded + uncoordinated.

Solution

It causes unbounded-height errors, conflicting physics, and double scrollbars, and the regions don't coordinate. Use one CustomScrollView with multiple slivers instead.

Q78. [Medium] (Coding) Fill leftover viewport space with a footer. Which sliver?

Hint

FillRemaining.

Solution

SliverFillRemaining(child: Footer()).

Q79. [Advanced] (Theory) What does SliverGeometry.paintExtent represent vs scrollExtent?

Hint

Visible vs total.

Solution

scrollExtent = the sliver's total length along the scroll axis; paintExtent = how much of it is currently visible in the viewport.

Q80. [Medium] (Theory) Name three common slivers and their roles.

Hint

AppBar/List/Grid.

Solution

SliverAppBar (collapsing header), SliverList (lazy list), SliverGrid (lazy grid). Also SliverToBoxAdapter, SliverPadding, SliverFillRemaining, SliverPersistentHeader.

Q81. [Advanced] (Coding) A SliverList with children: [...] is janky on a big list. Why and fix?

Hint

Fixed list builds all.

Solution

A fixed children: list builds everything up front (no laziness). Switch to a SliverChildBuilderDelegate so only visible rows build.

Q82. [Medium] (Theory) What gives you full control over a pinning/floating custom header?

Hint

Persistent header.

Solution

SliverPersistentHeader (with a SliverPersistentHeaderDelegate) — custom header that can pin/float with explicit min/max extents.

Q83. [Advanced] (Theory) Why can a collapsing header + list + grid scroll as one with slivers but not with nested ListViews?

Hint

One viewport.

Solution

Slivers share one viewport (one scroll position/belt), so their geometry coordinates. Nested ListViews are separate viewports with independent scroll, so they can't collapse/scroll together.


Section F — RenderObject (Q84–100)

Q84. [Basic] (Theory) What three jobs does a RenderObject do?

Hint

Layout/paint/hit-test.

Solution

Layout (size + place children), paint (draw), hit testing (point-inside). RenderBox is the common 2D base. (Part 6)

Q85. [Basic] (Theory) State the layout protocol.

Hint

Down/up/parent.

Solution

Constraints go down, sizes go up, the parent sets position. Parent passes BoxConstraints; child picks a Size; parent sets the child's offset.

Q86. [Medium] (Theory) Who chooses size and who chooses position?

Hint

Child vs parent.

Solution

The child chooses its size (within constraints); the parent chooses the child's position.

Q87. [Medium] (Theory) markNeedsLayout vs markNeedsPaint — when each?

Hint

Size vs appearance.

Solution

markNeedsLayout when size/position changes; markNeedsPaint when only appearance changes (e.g. color). Repaint-only is cheaper.

Q88. [Medium] (Coding) In a custom RenderBox, a color setter should call which invalidation?

Hint

Paint only.

Solution

markNeedsPaint() — color doesn't affect size/position, so no relayout.

Q89. [Medium] (Theory) What methods does a RenderObjectWidget implement, and when are they called?

Hint

create / update.

Solution

createRenderObject (once, on mount) and updateRenderObject (on rebuild when the Element is reused) — the latter mutates the render object in place.

Q90. [Advanced] (Theory) How does updateRenderObject explain cheap rebuilds?

Hint

Mutate in place.

Solution

When canUpdate reuses the Element, updateRenderObject updates the existing render object with new values instead of allocating a new one — so a full build() translates to minimal real work.

Q91. [Basic] (Theory) Name the three RenderObjectWidget base classes by child count.

Hint

Leaf/Single/Multi.

Solution

LeafRenderObjectWidget (0), SingleChildRenderObjectWidget (1), MultiChildRenderObjectWidget (many).

Q92. [Medium] (Theory) What is parentData used for?

Hint

Position/extra info.

Solution

It stores per-child data the parent owns — most commonly the child's offset (position), and layout extras (e.g. flex). The parent reads/writes it during layout/paint.

Q93. [Medium] (Coding) When should you use CustomPaint instead of a custom RenderBox?

Hint

Drawing, not layout.

Solution

When you need custom drawing with no special layout — a CustomPainter is simpler and sufficient (charts, gauges, signatures).

Q94. [Medium] (Theory) Why does child.layout(..., parentUsesSize: true) matter?

Hint

Relayout propagation.

Solution

It declares that the parent's layout depends on the child's size, so when the child relays out the parent relays out too. Omitting it (when you don't read child size) limits relayout propagation — an optimization.

Q95. [Advanced] (Theory) Why are layout and paint separate phases?

Hint

Independent invalidation.

Solution

Many changes affect only appearance, not geometry. Separate phases let Flutter repaint without relaying out (and vice versa), invalidating each independently to avoid unnecessary work.

Q96. [Medium] (Theory) What does RepaintBoundary do?

Hint

Own layer.

Solution

Isolates a subtree onto its own paint layer, so its repaints don't force neighbors to repaint (and vice versa).

Q97. [Advanced] (Coding) A small spinner forces a heavy static chart to repaint each frame. Fix.

Hint

Wrap it.

Solution

Wrap the spinner in RepaintBoundary so its repaints stay on its own layer and don't dirty the chart's layer.

Q98. [Advanced] (Theory) Why do "unbounded constraints" errors happen, in protocol terms?

Hint

Infinite maxHeight.

Solution

A parent (e.g. Column) passes unbounded (infinity) max constraints down to a child that wants to fill available space (e.g. ListView). The child can't pick a finite size, so layout fails. Give it bounded constraints (Expanded, SizedBox, etc.). (constraints)

Q99. [Medium] (Theory) How does hit testing work at the render layer?

Hint

Walk + point-inside.

Solution

Flutter walks the render tree asking each object (via hitTest) whether the pointer position falls inside it (typically against its size), building the list of objects under the pointer that then receive the event.

Q100. [Advanced] (Theory) Tie the whole series together: trace a Text color change from widget to pixel.

Hint

Every layer.

Solution

setState mutates a field and markNeedsBuilds the Element (Part 1) → BuildOwner rebuilds it → build returns a new TextcanUpdate matches (type+key) so the Element is reused → updateRenderObject sets the new color on the existing RenderParagraph and calls markNeedsPaint (no relayout, since size is unchanged) → the paint phase redraws just that render object's layer. Widget → Element → RenderObject, mutated in place.


Coding Mini-Exercises

Ten larger problems. Try each before opening the solution. Exercise 10 is a capstone.

Exercise 1 — Reorder bug. Show the minimal change that fixes state sticking to the wrong row when a stateful list reorders.

Show solution

Add an identity key to each item widget:

for (final item in items) ItemTile(key: ValueKey(item.id), item: item)

Now reconciliation matches by key, so state follows the item, not the slot (Part 2).

Exercise 2 — canUpdate table. For transitions (a) Text('a')Text('b'), (b) Text('a')SizedBox(), (c) Box(key: k1)Box(key: k2), (d) Box(key: k1)Box(key: k1), state reuse/replace and whether state survives.

Show solution

(a) reuse, state survives. (b) replace (type), state lost. (c) replace (key differs), state lost. (d) reuse, state survives. Driven by runtimeType + key (Part 1).

Exercise 3 — Builder fix. Rewrite so ScaffoldMessenger.of(context) works inside a page that returns a Scaffold.

Show solution
Scaffold(
  body: Builder(builder: (context) => ElevatedButton(
    onPressed: () => ScaffoldMessenger.of(context)
        .showSnackBar(const SnackBar(content: Text('Hi'))),
    child: const Text('Show'),
  )),
);

The Builder provides a context below the Scaffold (Part 3).

Exercise 4 — Lifecycle correctness. Write initState/didUpdateWidget/dispose for a widget that listens to widget.channel and must follow channel changes leak-free.

Show solution
void initState() { super.initState(); _sub = widget.channel.listen(_on); }
void didUpdateWidget(W o) { super.didUpdateWidget(o);
  if (o.channel != widget.channel) { _sub.cancel(); _sub = widget.channel.listen(_on); } }
void dispose() { _sub.cancel(); super.dispose(); }

initState sets up, didUpdateWidget reconciles the changed prop (same State reused), dispose prevents the leak (Part 4).

Exercise 5 — Async guard. Add the correct guard around a post-await navigation in a State.

Show solution
await api.submit();
if (!mounted) return;          // State.mounted
Navigator.of(context).pop();

After await the State may be disposed; mounted prevents "deactivated widget" errors (Part 3/Part 4).

Exercise 6 — Coordinated scroll. Build a CustomScrollView with a pinned collapsing header, a lazy list of 1000 items, and a footer filling the rest.

Show solution
CustomScrollView(slivers: [
  const SliverAppBar(expandedHeight: 180, pinned: true,
      flexibleSpace: FlexibleSpaceBar(title: Text('Feed'))),
  SliverList(delegate: SliverChildBuilderDelegate(
      (c, i) => ListTile(title: Text('Item $i')), childCount: 1000)),
  const SliverFillRemaining(hasScrollBody: false, child: Footer()),
]);

One viewport, lazy list, all scrolling together (Part 5).

Exercise 7 — Custom RenderBox. Write a RenderBox that is a fixed 100×100 square of a settable color, repainting (not relaying out) on color change.

Show solution
class RenderSquare extends RenderBox {
  RenderSquare(this._c);
  Color _c;
  set color(Color v) { if (v == _c) return; _c = v; markNeedsPaint(); }
  @override
  void performLayout() => size = constraints.constrain(const Size(100, 100));
  @override
  void paint(PaintingContext context, Offset offset) =>
      context.canvas.drawRect(offset & size, Paint()..color = _c);
}

Color setter calls markNeedsPaint only — no relayout (Part 6).

Exercise 8 — Wrap it as a widget. Wire the RenderSquare from Exercise 7 into a LeafRenderObjectWidget.

Show solution
class Square extends LeafRenderObjectWidget {
  const Square(this.color, {super.key});
  final Color color;
  @override
  RenderObject createRenderObject(BuildContext c) => RenderSquare(color);
  @override
  void updateRenderObject(BuildContext c, RenderSquare ro) => ro.color = color;
}

createRenderObject once; updateRenderObject mutates in place on reuse (Part 6).

Exercise 9 — Contain repaints. A CustomScrollView has an animated banner among static cards that repaint every frame in profiling. Apply the fix.

Show solution

Wrap the animating banner (and/or the expensive static cards) in RepaintBoundary so the animation's repaints stay on its own layer and don't dirty the cards (Part 6).

Exercise 10 — Capstone: trace a tap, end to end. A user taps a "like" button in a reorderable feed list, incrementing a count shown on that row. Trace the interaction through every layer of the series, naming the mechanism at each step.

Show solution
  1. Hit testing (Part 6): the tap location walks the render tree; the like button's render object is found under the pointer and the gesture is dispatched.
  2. Callback → setState (Part 4/State Mgmt 1): the row's State increments its count inside setState, which calls markNeedsBuild on its Element.
  3. BuildOwner (Part 1): the dirty Element is rebuilt next frame in tree order.
  4. Reconciliation / canUpdate (Part 1): the new widget subtree matches the old by runtimeType+key, so Elements are reused, not rebuilt. Because the list has keys (Part 2), the correct row's Element is matched by identity even though the list can reorder.
  5. updateRenderObject (Part 6): the count Text's render object has its text updated in place and calls markNeedsPaint (size unchanged → no relayout, just repaint).
  6. BuildContext (Part 3): any Theme.of(context) the row uses resolves upward to style the new text.
  7. Layout (if size changed) (Part 6): if the new number is wider, the protocol runs — constraints down, sizes up, parent positions — but for a same-width change only paint runs.
  8. Slivers (Part 5): because the feed is a lazy SliverList, only on-screen rows ever existed as Elements/RenderObjects, so this whole process touched a screen's worth of objects, not the whole list.
  9. Lifecycle/leak safety (Part 4): if the like triggered async work, the callback guards with mounted before any further setState.

One tap, every layer: hit test → callback → markNeedsBuild → BuildOwner → canUpdate (+keys) → updateRenderObjectmarkNeedsPaint → composited frame. If you can narrate this, you understand Flutter internals.


You made it

A hundred questions, ten exercises, and an end-to-end capstone. If you worked them honestly, you can now explain Flutter's engine the way interviewers hope candidates can:

  • Part 1 — Three trees: Widgets, Elements, canUpdate, BuildOwner.
  • Part 2 — Keys: controlling reconciliation identity.
  • Part 3 — BuildContext: it's the Element; lookups go up.
  • Part 4 — Lifecycle: initState → didUpdateWidget → dispose.
  • Part 5 — Slivers: the viewport/sliver scrolling engine.
  • Part 6 — RenderObject: layout, paint, and below.

Pair this with Flutter Fundamentals, State Management, and the Dart Internals series, and you understand Flutter top to bottom. Now go read the framework source — it'll finally make sense. 🚀