Beginner Fundamentals
What is a widget in Flutter?
A widget is an immutable description of part of the UI — configuration, not the actual rendered pixels. Everything on screen, including layout, padding, and text, is a widget, and Flutter rebuilds a new widget tree (cheaply, since widgets are lightweight) whenever the UI needs to reflect new data.
What is the difference between StatelessWidget and StatefulWidget?
A StatelessWidget is built once from its constructor inputs and never changes on its own. A StatefulWidget creates a companion State object that can hold mutable data and call setState() to trigger a rebuild — used whenever a widget owns data that changes over its lifetime, like a counter or a toggle.
What does setState() actually do?
It marks the associated State object as "dirty" and schedules a call to its build() method on the next frame, so the widget subtree re-renders with the updated data. The function passed to setState should synchronously update the state fields — any async work should happen before calling setState, not inside it.
What is the purpose of the build() method?
build() describes what a widget's UI should look like right now, returning a tree of other widgets. It can be called many times during a widget's lifetime (any time it needs to rebuild), so it should be fast and free of side effects like network calls.
What is the difference between hot reload and hot restart?
Hot reload injects updated source code into the running Dart VM and rebuilds the widget tree, preserving app state (like the current screen and form values) — fast, ideal for UI tweaks. Hot restart destroys and recreates the entire app state from scratch, reloading the code fully — needed when reload can't apply the change, e.g. after modifying main(), global variables, or enum values.
What does the const keyword do when used on a widget?
It creates a compile-time constant instance of the widget. Flutter can recognize that a const widget hasn't changed between rebuilds and skip rebuilding it entirely, which is a simple, effective performance optimization for static parts of the UI.
Intermediate Architecture & State
What are the widget tree, element tree, and render tree, and how do they relate?
The widget tree is the immutable configuration returned by build() methods. The element tree is a persistent, mutable structure Flutter maintains across rebuilds — each element links a widget to its position in the tree and, for stateful widgets, holds the State object. The render tree (RenderObjects) performs the actual layout, painting, and hit-testing. On rebuild, Flutter diffs the new widget tree against the existing element tree and only updates the render objects that actually changed, rather than recreating everything.
When and why would you use a Key?
A Key preserves widget identity and state across rebuilds when widgets of the same type could otherwise be ambiguously matched — most commonly in a reorderable or filterable list. Without a stable key (like ValueKey tied to an item's ID), Flutter matches widgets by position, which can cause state (like a text field's cursor or a checkbox's value) to attach to the wrong item after a reorder or removal.
What is the difference between Provider's context.watch() and context.read()?
context.watch<T>() subscribes the calling widget to changes on T, causing a rebuild whenever it notifies listeners — used inside build(). context.read<T>() retrieves the current value without subscribing to changes — used inside callbacks (like onPressed) where you want to call a method or read a value once, without triggering unnecessary rebuilds.
Why must TextEditingControllers and AnimationControllers be disposed?
Both hold native/platform resources and listener subscriptions that aren't automatically cleaned up when a widget is removed from the tree. Failing to call .dispose() in the State's dispose() method leaks memory and can cause "used after disposed" errors if a lingering listener fires after the widget is gone.
What's the difference between MediaQuery and LayoutBuilder for responsive UI?
MediaQuery.of(context).size gives the full screen/window dimensions, useful for screen-level responsive decisions. LayoutBuilder gives the constraints passed down from the immediate parent widget, which is more accurate for a widget that needs to adapt to the space it's actually given (e.g. inside a side panel that isn't full-width), since that may differ from the overall screen size.
How does async* / yield differ from a regular Future-returning function?
An async* function returns a Stream and can yield multiple values over time, one at a time, as they become available — suited for ongoing data like a countdown or live updates. A regular async function returns a single Future that resolves once with one final value.
Advanced Deep Dives
How does Flutter's rendering pipeline work, from setState to pixels on screen?
Calling setState marks the element dirty and schedules a frame. On the next frame, Flutter's build phase re-runs build() for dirty elements and reconciles the resulting widget tree against the existing element tree (diffing by type and key) to update the element/render tree minimally. The layout phase then has each RenderObject compute its size given constraints from its parent (a single top-down, bottom-up pass). The paint phase records drawing instructions into layers. Finally, the compositing/rasterization stage (on the engine's raster thread, via Skia/Impeller) turns those layers into actual pixels — kept separate from the UI thread so a busy build doesn't necessarily stall compositing of already-painted layers.
What is the difference between InheritedWidget and Provider, and how does Provider actually work under the hood?
InheritedWidget is the low-level primitive Flutter provides for efficiently propagating data down the tree — descendants that call context.dependOnInheritedWidgetOfExactType register as dependents and only rebuild when updateShouldNotify returns true. Provider is a wrapper package built on top of InheritedWidget (specifically a variant that supports arbitrary object types, not just widgets) that adds convenient APIs (context.watch/read/select), lifecycle management (creating and disposing objects), and combinators for exposing multiple values — but the underlying rebuild-on-change mechanism is still InheritedWidget's dependency tracking.
How would you diagnose and fix janky scrolling in a ListView.builder with complex item widgets?
Use the DevTools Performance view (or the in-app Performance Overlay) to see whether frames are exceeding the 16ms budget, and whether the cost is in build/layout/paint or raster. Common fixes: extract item widgets into their own const-friendly classes so unrelated rebuilds don't force them to rebuild, add itemExtent or a prototypeItem when rows have a fixed height (avoids layout having to measure every item), avoid expensive work (image decoding, complex gradients) directly in build() — precompute or cache it — and use RepaintBoundary around items whose painting is expensive but whose bounds rarely need to repaint together with siblings.
What is the difference between compile-time (AOT) and JIT compilation in Flutter, and when is each used?
During development, Flutter uses JIT (just-in-time) compilation, which enables hot reload by recompiling and injecting changed code into a running Dart VM. Release builds use AOT (ahead-of-time) compilation, compiling Dart directly to native machine code (ARM/x64) with no VM/interpreter overhead at runtime, which is why release performance is close to native and why hot reload is unavailable in release mode.
How does Flutter achieve consistent rendering across platforms without using native UI components?
Flutter doesn't wrap native platform widgets — it draws every pixel itself using its own rendering engine (Skia historically, Impeller on newer versions) directly onto a canvas/surface the platform provides, bypassing platform UI toolkits (UIKit widgets, Android Views) entirely. This is why Flutter UI looks and behaves identically across platforms, at the cost of needing to reimplement platform look-and-feel (e.g. Cupertino vs Material widgets) rather than getting it for free, and needing platform channels to access native APIs Flutter doesn't handle itself.