116 Flutter Interview Questions and Answers (2026)

More teams ship production apps with Flutter every year, and interview bars have climbed with it. Recruiters no longer accept "I've built a few screens"—they probe how widgets rebuild, how state flows, and how Dart handles null safety and async. Walk in shaky on those, and a stronger candidate takes the offer.
This guide gives you 116 questions with concise, interview-ready answers and code where it counts. It's worked Junior to Mid to Senior, so you start with Dart and widget fundamentals and build toward rendering, native interop, and performance. Work through it and you'll answer with real fluency, not guesses.
Q1.What is the difference between final and const in Dart, and how do they affect memory allocation?
final and const in Dart, and how do they affect memory allocation?Both create immutable bindings, but final is a runtime constant (set once, at runtime) while const is a compile-time constant that is canonicalized and baked into the program.
final:
Value can be computed at runtime (e.g. DateTime.now()) but assigned only once.
Each final object is a distinct allocation on the heap.
const:
Must be fully known at compile time; the whole object graph must also be const.
Canonicalized: identical const expressions share one memory instance, saving allocations.
Memory impact: const objects are created once and reused, reducing GC pressure; in Flutter, const widgets let the framework skip rebuilding unchanged subtrees.
Q2.Explain the difference between the ! (bang) operator and the ? operator. When would you use the late keyword instead of making a variable nullable?
! (bang) operator and the ? operator. When would you use the late keyword instead of making a variable nullable?The ? operators handle uncertainty safely (a value might be null), while ! asserts to the compiler that a nullable value is definitely non-null right now, bypassing the check at your own risk.
? family (safe):
String? marks a type as nullable.
?. null-aware access returns null instead of crashing; ?? supplies a default.
! (bang, unsafe): Casts T? to T; if the value is actually null it throws at runtime. Use only when you can guarantee non-null.
late vs nullable:
Choose late when the variable is conceptually always non-null but initialized later (e.g. in initState or dependency injection).
Choose nullable when null is a genuine, valid state you must handle, so you avoid scattering ! everywhere.
Q3.Explain the difference between positional, named, and optional parameters in Dart. What does the 'required' keyword do?
required' keyword do?Dart parameters come in three flavors: positional (matched by order), named (matched by name), and optional (may be omitted). The required keyword forces a named parameter to be provided at the call site, giving null-safety guarantees without a default.
Positional (required by default): Declared in order, passed in order: void greet(String name, int age).
Optional positional (in square brackets): Wrapped in [ ]; can be omitted and default to null or a given default: void log(String msg, [int level = 0]).
Named (in curly braces):
Wrapped in { } and passed by name in any order: improves readability, common in widget constructors.
Optional by default unless marked required or given a default value.
The required keyword:
Applies to named parameters: makes a non-nullable named parameter mandatory so the compiler enforces it.
Without it, a non-nullable named param would need a default value to satisfy null safety.
Q4.What is cascade notation (..) in Dart, and when would you use it?
..) in Dart, and when would you use it?Cascade notation (..) lets you perform a sequence of operations on the same object without repeating the object reference. Each cascaded call returns the original object, not the result of the call, so you can configure or mutate an object fluently.
How it works:
..method() calls the method but evaluates to the receiver, so calls chain on the same instance.
Use ?.. for a null-aware cascade that only runs if the target is non-null.
When to use it:
Configuring an object right after creation (setting several properties or calling several setup methods).
Building lists or controllers where you'd otherwise assign to a temp variable repeatedly.
Contrast with a builder that returns this: cascades work even on APIs whose methods return void.
Q5.What are collection-if, collection-for, and the spread operator, and how do they help when building widget lists?
collection-if, collection-for, and the spread operator, and how do they help when building widget lists?Collection-if, collection-for, and the spread operator are collection literals features that let you build lists (or sets/maps) declaratively, without breaking out into imperative code. They shine in Flutter children: arrays where you conditionally include or generate widgets.
Collection-if:
Conditionally include an element: if (isLoggedIn) LogoutButton() inside a list literal.
Supports else too.
Collection-for: Generate elements from an iterable inline: for (final item in items) Text(item).
Spread operator:
... inlines all elements of another collection into this one.
Null-aware ...? skips a null collection safely.
Why it helps in widget lists: Keeps children declarative and readable instead of building a list with a separate variable and loops.
Q6.Explain the null-aware operators in Dart (??, ??=, ?.). How do they help write safer code?
??, ??=, ?.). How do they help write safer code?Null-aware operators let you handle nullable values concisely instead of writing verbose null checks. Combined with Dart's sound null safety, they make it explicit where nulls can appear and provide safe fallbacks.
?? (if-null / null-coalescing): Returns the left operand if non-null, otherwise the right: name ?? 'Guest'.
??= (null-aware assignment): Assigns only if the variable is currently null: cache ??= compute() (lazy initialization).
?. (null-aware access): Calls a member only if the receiver is non-null, otherwise the whole expression is null: user?.address?.city.
Why safer: They short-circuit null instead of throwing, avoiding null-dereference errors and reducing boilerplate if (x != null) blocks.
Q7.What is the difference between var, final, const, and dynamic in Dart, and how does type inference work?
var, final, const, and dynamic in Dart, and how does type inference work?All four affect how a variable is typed and whether it can change. var and dynamic both let you skip writing the type, but var infers a fixed static type while dynamic disables type checking. final and const both prevent reassignment, differing in when the value is known.
var: Type inferred once from the initializer and then fixed; the value can still be reassigned to the same type.
final: Single-assignment: set once at runtime, cannot be reassigned. The object itself can still be mutated.
const: Compile-time constant: value must be known at compile time and is deeply immutable. Enables canonicalization (identical const values share one instance).
dynamic: Opts out of static type checking; member calls are resolved at runtime and can throw. Use sparingly.
Type inference: The compiler derives the type from the initializer, so var count = 0 is exactly an int; no initializer means it falls back to dynamic.
Q8.What is the difference between a StatelessWidget and a StatefulWidget at a conceptual level?
StatelessWidget and a StatefulWidget at a conceptual level?A StatelessWidget describes UI that depends only on its configuration and never changes on its own, while a StatefulWidget can hold mutable state that changes over its lifetime and triggers rebuilds.
StatelessWidget:
Immutable: given the same inputs, it always renders the same output.
Rebuilt only when its parent passes new configuration.
Good for pure, presentational UI (icons, labels, static layouts).
StatefulWidget:
Pairs an immutable widget with a separate mutable State object that persists across rebuilds.
Calling setState() marks it dirty and schedules a rebuild.
Has lifecycle hooks (initState, dispose) for setup and cleanup.
Conceptual key: Both widgets are immutable configuration; the difference is that a StatefulWidget delegates changing data to a long-lived State object living in the Element tree.
Q9.Explain the difference between const and final. Why does the Flutter compiler prefer const constructors for widgets?
const and final. Why does the Flutter compiler prefer const constructors for widgets?Both final and const mean the variable can't be reassigned, but const is a stronger, compile-time constant while final is set once at runtime.
final:
Assigned once, but the value can be computed at runtime (e.g. final now = DateTime.now();).
The reference is fixed; the object itself may still be mutable.
const:
Value must be known at compile time and is deeply immutable.
Identical const values are canonicalized to a single shared instance.
Why the compiler prefers const constructors for widgets:
A const widget is built once and reused, so rebuilds can skip re-creating it.
During diffing, an unchanged const widget is identical to its previous instance, letting Flutter prune that subtree from rebuilding.
Q10.What is the difference between SizedBox and Container, and when is one more appropriate than the other?
SizedBox and Container, and when is one more appropriate than the other?SizedBox is a lightweight box for fixed sizing or spacing, while Container is a convenience widget bundling sizing, padding, margins, decoration, and alignment. Use the simplest one that does the job.
SizedBox:
Just constrains width/height (or adds empty space between widgets).
Can be const, so it's cheaper and preferable for fixed gaps: const SizedBox(height: 16).
Container:
A composition of multiple widgets (Padding, DecoratedBox, Align, ConstrainedBox) behind one API.
Use when you need background color, borders, rounded corners, padding, or margins.
When to pick which:
Only need spacing or a fixed size: use SizedBox.
Need decoration/padding/margin together: use Container.
Avoid an empty Container() for spacing: it's heavier than a SizedBox.
Q11.What is 'everything is a widget' meant to convey, and how does Flutter differ from platforms that separate views, controllers, and layouts?
"Everything is a widget" means Flutter uses one uniform building block for UI, layout, styling, and even structural concerns, rather than separate primitive types for views, layouts, and controllers. You compose widgets all the way down.
What it conveys:
Not just visible things: padding, alignment, gesture handling, and even the app itself (MaterialApp) are widgets.
One consistent composition model, so the same nesting technique builds everything.
How it differs from view/controller/layout platforms:
Traditional stacks (Android XML layouts + Activities, or UIKit views + view controllers) separate layout files, view classes, and controller logic into distinct concepts.
Flutter collapses these into a single declarative widget tree described in code.
Layout is expressed by wrapping widgets, not by a separate constraint/layout language.
Underlying nuance: Widgets are just lightweight configuration; the real work happens in the Element and RenderObject trees the framework manages beneath them.
Q12.What is the difference between 'Ephemeral' (local) state and 'App' (global) state? Give an example of when setState is sufficient.
setState is sufficient.Ephemeral (local) state is UI state owned by a single widget that no other part of the app needs; app (global) state is shared data that multiple widgets or screens read and mutate. The dividing line is scope, not importance.
Ephemeral (local) state:
Lives inside a State object and is managed with setState.
Examples: current page in a BottomNavigationBar, the open/closed state of a dropdown, animation progress, a text field's current value.
App (global) state:
Shared across widgets/routes and often outlives a single screen.
Examples: logged-in user, shopping cart, theme, cached API data. Managed with Provider, Riverpod, Bloc, etc.
When setState is sufficient:
When the state is only consumed within the widget that holds it: e.g. a checkbox toggling its own checked value, or a counter shown only on that screen.
No other widget needs to know, so lifting the state or adding a management library is unnecessary overhead.
Q13.What actually happens when you call setState()? Why is it discouraged to call it inside the build method?
setState()? Why is it discouraged to call it inside the build method?Calling setState() runs the callback you give it (to mutate fields) and then marks the Element as dirty, scheduling a rebuild on the next frame. It doesn't rebuild immediately or synchronously.
What actually happens:
Your callback runs synchronously, applying the state change.
The element is marked dirty via markNeedsBuild().
Flutter schedules a frame; on the next pipeline flush, build() re-runs and the tree reconciles.
Why not call it inside build():
It creates an infinite loop: build marks dirty, which schedules another build, forever.
build is meant to be a pure function of state and props with no side effects; mutating state there breaks that contract.
Put side effects and state changes in event handlers or lifecycle methods instead.
Notes:
Mutate the fields inside the callback, not before, so Flutter sees a consistent state.
Calling setState after dispose throws; guard with mounted for async callbacks.
Q14.What work belongs in initState versus dispose, and why is it important to dispose controllers and stream subscriptions?
initState versus dispose, and why is it important to dispose controllers and stream subscriptions?Put one-time setup that a widget owns for its whole life in initState, and release everything you created in dispose. Disposing controllers and subscriptions is essential to avoid memory leaks and callbacks firing on a dead widget.
Belongs in initState:
Creating controllers (AnimationController, TextEditingController, ScrollController).
Opening stream/listener subscriptions, one-time data fetches, initializing non-context state.
Belongs in dispose:
Calling .dispose() on controllers and .cancel() on StreamSubscriptions.
Removing listeners and closing anything you opened.
Why disposal matters:
Undisposed objects keep references alive, leaking memory (tickers keep animating, streams keep emitting).
A live subscription may call setState on an unmounted widget, causing exceptions.
Symmetry rule: whatever you allocate in initState should be torn down in dispose.
Q15.What is the difference between using async/await and chaining .then() on a Future?
async/await and chaining .then() on a Future?Both handle the same asynchronous result; the difference is readability and error handling. async/await lets you write asynchronous code that reads top-to-bottom like synchronous code, while .then() chains callbacks. They are functionally equivalent under the hood.
Readability:
await avoids nested callbacks ("callback hell") and keeps sequential logic linear.
.then() can get awkward when one result depends on the previous, requiring nested or chained callbacks.
Error handling:
With await you use ordinary try/catch.
With .then() you pass an .catchError() (and often .whenComplete()).
Same mechanism:
An async function always returns a Future; await is essentially syntactic sugar over .then().
.then() still shines for simple fire-and-transform pipelines where you don't want to mark the function async.
Q16.What is the difference between Expanded and Flexible when used inside a Row or Column?
Expanded and Flexible when used inside a Row or Column?Both distribute leftover space along the main axis to children, but Expanded forces the child to fill its share while Flexible lets the child be smaller than its share. In fact Expanded is just Flexible with fit: FlexFit.tight.
Expanded (FlexFit.tight): Gives the child a tight constraint: it must occupy exactly its allotted flex portion.
Flexible (FlexFit.loose by default): Gives a loose constraint: the child may take up to its share but can be smaller if its own size is less.
Both use the flex factor: Space is divided in proportion to each child's flex value (default 1).
Rule of thumb: use Expanded to fill/split space evenly; use Flexible when the child should shrink to its content but not overflow.
Q17.How does MainAxisSize.min vs MainAxisSize.max affect a Column or Row?
MainAxisSize.min vs MainAxisSize.max affect a Column or Row?MainAxisSize controls how much space the Row/Column itself tries to occupy along the main axis: max expands to fill all available space, while min shrinks to wrap its children.
MainAxisSize.max (default):
The Row/Column takes all the space its parent offers on the main axis.
Required for MainAxisAlignment like spaceBetween or center to have room to work.
MainAxisSize.min:
The Row/Column is only as big as the sum of its children.
Use when you want the widget to hug its content (e.g. a Row inside a button or a centered dialog).
Caveat: MainAxisSize.min with an Expanded child conflicts, since Expanded needs bounded space to fill.
Q18.How does the Stack widget work with Positioned, and when would you reach for a Stack over a Row or Column?
Stack widget work with Positioned, and when would you reach for a Stack over a Row or Column?A Stack layers children on top of one another, and Positioned pins a child to specific edges within the stack. Reach for a Stack when widgets must overlap, which Row/Column (linear, non-overlapping layout) cannot express.
How Stack layers work:
Children paint in order, last child on top.
Non-positioned children are sized/aligned by the stack's alignment and determine the stack's size.
Positioned:
Sets top, right, bottom, left, width, or height relative to the stack's edges.
Only usable inside a Stack; a non-Positioned child ignores these.
When to choose Stack over Row/Column:
Overlapping UI: badges on avatars, text over an image, floating buttons, gradient overlays.
Use clipBehavior to control whether children spilling outside are clipped.
Q19.What does the Wrap widget do, and how does it differ from a Row or Column that overflows?
Wrap widget do, and how does it differ from a Row or Column that overflows?Wrap lays children out in a line like a Row/Column but wraps to a new line (or column) when it runs out of space, instead of overflowing. A Row or Column has a single line and will render an overflow error when children exceed the available main-axis space.
Wrap flows onto multiple runs:
When the current line is full, the next child starts a new run.
spacing controls gaps within a run; runSpacing controls gaps between runs.
Row/Column overflows: They never wrap; excess content triggers the yellow-and-black overflow warning unless made scrollable or flexible.
Trade-off:
Wrap is ideal for a variable number of same-ish items like chips or tags.
It doesn't support Expanded/flex, since items are sized by content.
Q20.How do you return a result from a pushed route back to the previous screen?
You return a value by awaiting Navigator.push on the caller side and passing that value to Navigator.pop on the pushed screen. push returns a Future that completes with whatever the popped route hands back.
Caller awaits the result:
`push`/`pushNamed` return Future<T?>; await it to get the value.
The result is nullable: if the user pops with the back button or gesture, you get null.
Pushed screen returns the value: Call Navigator.pop(context, result).
Type safety: parameterize the push with the expected type, e.g. push<bool>, so the returned value is typed.
Handle the null case in the UI (user dismissed without choosing).
Q21.Why should you avoid calling heavy computational logic inside a build() method?
build() method?Because build() can be called very frequently (every frame during an animation, on every parent rebuild), any heavy work inside it runs repeatedly on the UI thread and causes dropped frames and jank.
build() is not a one-time setup: Flutter may call it many times per second; it should be cheap and side-effect free.
Heavy work blocks the UI thread: Parsing, sorting large lists, JSON decode, regex, or file I/O inside build() steals from the 16ms frame budget and drops frames.
Where the work belongs instead:
One-time computation: initState or a memoized field.
Async or CPU-bound work: a Future, an Isolate / compute(), cached in state.
Derived data: precompute in your state/view-model, not per build.
Rule of thumb: build() should only assemble already-computed data into widgets.
Q22.What is the difference between hot reload and hot restart? What happens to the state in each?
Hot reload injects updated source code into the running Dart VM and rebuilds the widget tree, preserving app state; hot restart tears down the running app and rebuilds it from scratch, discarding all state.
Hot reload:
Recompiles changed files and pushes them into the running Dart VM, then triggers a rebuild.
App state is preserved: variables in memory, current route, and widget state survive.
Limitations: it re-runs build() but not main() or initState(); changes to global state, static fields, or enum/class shape may need a restart.
Hot restart:
Destroys the widget tree and restarts the app from main().
All in-memory state is lost: you return to the app's initial state.
Slower than hot reload but faster than a full rebuild since the app is not recompiled fully from native side.
Rule of thumb: use hot reload for UI tweaks, hot restart when you change app initialization or global/static state.
Q23.What is the difference between a Plugin and a Package in the Flutter ecosystem?
Both are Dart packages published to pub.dev, but a package is pure Dart/Flutter code, while a plugin is a specialized package that also includes platform-specific native code to access device capabilities.
Package (Dart package):
Contains only Dart code: utilities, widgets, state management, models.
Examples: provider, http, intl.
Plugin (plugin package):
A package that bridges to native platform APIs via MethodChannel / platform channels.
Includes native code (Kotlin/Java, Swift/Objective-C, etc.) alongside Dart.
Examples: camera, geolocator, shared_preferences.
Key distinction: every plugin is a package, but not every package is a plugin. A plugin exists specifically when you must reach platform-native functionality.
Q24.What is the purpose of pubspec.yaml, and how does semantic versioning of dependencies work in the pub ecosystem?
pubspec.yaml, and how does semantic versioning of dependencies work in the pub ecosystem?pubspec.yaml is the project's manifest: it declares metadata, dependencies, and assets. Version constraints follow semantic versioning (MAJOR.MINOR.PATCH) so pub can resolve compatible package versions.
What pubspec.yaml defines:
App name, description, and version.
Dependencies (dependencies, dev_dependencies) and the Dart/Flutter SDK constraint.
Assets, fonts, and flutter-specific config.
Semantic versioning (MAJOR.MINOR.PATCH): MAJOR: breaking changes; MINOR: backward-compatible features; PATCH: backward-compatible fixes.
Caret constraints:
^1.2.3 means ">=1.2.3 <2.0.0": accept any non-breaking update below the next major.
For versions below 1.0.0, ^0.2.3 locks the minor: ">=0.2.3 <0.3.0".
Lockfile: pubspec.lock pins the exact resolved versions so builds are reproducible across machines.
Q25.Explain the purpose of BuildContext. What does it actually represent in the widget tree?
BuildContext. What does it actually represent in the widget tree?A BuildContext is a reference to the Element that a widget occupies in the tree: it represents that widget's specific position, letting it look up ancestors and services relative to where it lives.
What it actually is:
Every widget has a corresponding Element, and BuildContext is that element's interface (the Element class implements BuildContext).
So it encodes a location in the tree, not the widget's data.
What you use it for:
Ancestor lookups: Theme.of(context), MediaQuery.of(context), Navigator.of(context) walk up from this position to find the nearest matching ancestor.
Registering inherited-widget dependencies for rebuilds.
Why position matters:
A context only sees ancestors above it: using a context above a Provider or Scaffold fails to find it (a common Builder fix is to introduce a new context below the ancestor).
Its validity is tied to the element being mounted in the tree.
Q26.What are Records in Dart 3, and how do they differ from using a Map or a custom Class for returning multiple values?
Map or a custom Class for returning multiple values?Records are a lightweight, immutable, anonymous aggregate type introduced in Dart 3 that let you bundle multiple values into a single object without declaring a class. They shine for returning multiple values from a function.
Syntax and access:
Positional fields accessed via .$1, .$2; named fields via their name ((int, {String name})).
Immutable and value-based: two records are == if their fields match, unlike class instances by default.
vs Map: A Map is dynamically typed and stringly-keyed: no compile-time guarantee a key exists or its value type. Records are fully type-safe and checked at compile time.
vs custom Class:
A class needs a declaration, constructor, and boilerplate but gives a named identity, methods, and reusability.
Records have no name or behavior: use them for ephemeral groupings, use a class when the concept has identity or logic.
Pairs beautifully with pattern-matching destructuring.
Q27.What is the difference between == and identical() in Dart, and how does this relate to the use of const constructors?
== and identical() in Dart, and how does this relate to the use of const constructors?== tests value equality (whatever the class defines), while identical() tests reference identity (whether two references point to the exact same object in memory). const ties them together through canonicalization.
== (equality operator): Default implementation on Object falls back to identity, but classes can override it (e.g. value types).
identical() (reference check): A top-level function that returns true only if both arguments are the same instance; it can never be overridden.
Relationship to const:
const objects are canonicalized: identical const expressions with identical field values produce the same shared instance.
So two const objects with equal fields are identical(), which is why const widgets help Flutter skip rebuilds (Flutter can cheaply reuse the same instance).
Q28.What does 'Sound' Null Safety mean in Dart? Explain the difference between the late keyword and a nullable type.
late keyword and a nullable type.Sound null safety means the Dart type system guarantees that a non-nullable variable can never hold null: if a type isn't marked with ?, the compiler proves it's never null, eliminating an entire class of runtime null-dereference crashes.
"Sound" guarantee: The soundness is airtight: because null can't sneak in, the compiler can optimize and you get compile-time errors instead of runtime NPEs.
Nullable type (?): String? declares a variable that may legitimately be null; you must null-check before use.
late keyword:
Declares a non-nullable variable whose initialization is deferred; you promise to assign it before first read.
Enables lazy initialization and avoids nullability, but throws a LateInitializationError if read before assignment.
Rule of thumb: use ? when null is a valid value; use late when the value is never null but just isn't ready at declaration.
Q29.What does 'Sound' Null Safety actually mean in Dart? How does the late keyword interact with it, and what are the risks of using the ! operator?
late keyword interact with it, and what are the risks of using the ! operator?Sound null safety means the type system provably guarantees a non-nullable variable is never null, so null errors become compile-time errors rather than runtime crashes. late and ! are escape hatches that shift some of that guarantee onto you.
Why "sound": The guarantee holds everywhere with no gaps, so the compiler trusts non-null types and can optimize, and mixed null-safe/unsafe leaks are prevented.
late interaction:
Lets you keep a type non-nullable while deferring initialization; the compiler moves the check from compile time to a runtime assertion on first read.
Risk: reading before assignment throws LateInitializationError.
! risks:
It silences the compiler without proof; a wrong assumption produces a runtime null exception, the very thing null safety was meant to eliminate.
Overusing it effectively opts out of the safety net, so prefer proper null checks or ??.
Q30.Explain the concept of Sound Null Safety. How does it improve app stability and performance?
Sound null safety is Dart's compile-time guarantee that non-nullable types can never contain null, turning a common class of runtime crashes into errors caught before the app ships and letting the compiler generate faster code.
Improves stability:
The dreaded null-dereference crash is caught at compile time; you're forced to handle null explicitly where it can occur.
APIs become self-documenting: a ? in a signature signals exactly where null is allowed.
Improves performance:
Because the compiler knows a value can't be null, it can skip redundant null checks and generate leaner, faster machine code.
Smaller code size and better optimization opportunities during AOT compilation.
"Sound" is the key word: soundness means the guarantee is complete, which is what enables those optimizations safely.
Q31.How do generics work in Dart, and why are they useful when building reusable widgets or data classes?
Generics let you write a type parameterized over another type (<T>), so one implementation works with many types while keeping full compile-time type safety, avoiding both duplication and unsafe dynamic casts.
How they work:
A type parameter like T is a placeholder filled in at usage: List<int> vs List<String>.
Dart reifies generics: the type argument is preserved at runtime, so is checks and casts remain accurate.
You can constrain them with extends (e.g. T extends Widget) to require capabilities.
Why useful for widgets and data classes:
Reusable widgets: FutureBuilder<T> or a custom DropdownList<T> work with any data type while giving the builder a correctly typed value.
Data classes: an ApiResponse<T> or Result<T> wrapper carries a typed payload without rewriting per model.
Type safety catches mismatches at compile time and removes manual casting.
Q32.What are enhanced enums in Dart, and how do they differ from traditional enums?
Enhanced enums (Dart 2.17+) are enums that can declare fields, constructors, methods, and implement interfaces, turning them into full value types while keeping the fixed, exhaustive set of instances. Traditional enums were just a bare list of named constants.
Traditional enums: Only a list of names, exposing .index and .name; no data or behavior attached.
Enhanced enums add:
Final fields plus a const constructor to attach data to each value.
Methods and getters, and the ability to implement interfaces or use mixins.
Constraints: Constructors must be const; instances are still fixed and exhaustive, so switch over them stays checkable.
Q33.What are Sealed Classes in Dart, and how do they improve the exhaustiveness of switch statements or pattern matching?
switch statements or pattern matching?A sealed class (Dart 3) is an abstract class whose subclasses are all known within the same library. Because the compiler knows the complete set of subtypes, a switch over a sealed type can be checked for exhaustiveness: you get a compile-time error if you miss a case.
What sealed does:
Implicitly abstract and cannot be instantiated or extended outside its library.
Gives the compiler a closed, known set of direct subtypes.
Exhaustiveness benefit:
A switch (statement or expression) covering every subtype needs no default; adding a new subtype turns forgotten cases into compile errors.
Pairs naturally with pattern matching to destructure each case.
Common use: Modeling states like Loading, Success, Error in a type-safe way (algebraic data types).
Q34.What is the purpose of a Mixin (with keyword)? How does it differ from standard Class inheritance or implementing an Interface?
with keyword)? How does it differ from standard Class inheritance or implementing an Interface?A mixin is a reusable bundle of behavior (methods and state) that you fold into a class with the with keyword, without a traditional parent-child relationship. It solves code reuse across unrelated classes where single inheritance would be too restrictive.
Mixin (with):
Provides implementation you can reuse across many classes; a class can mix in several mixins (composition of behavior).
Can restrict use with on to require a superclass, and can call super methods (linearization).
vs class inheritance (extends): You can only extends one class; mixins let you reuse code from multiple sources without that limit.
vs interface (implements): An interface gives only a contract you must fully implement; a mixin supplies the actual implementation for free.
Rule of thumb: Use a mixin for "has-a-capability" shared behavior; inheritance for "is-a" hierarchies; interfaces for pure contracts.
Q35.What is the difference between the base, interface, final, and sealed class modifiers introduced in Dart 3?
base, interface, final, and sealed class modifiers introduced in Dart 3?These class modifiers control how a class can be used outside its own library: whether it can be constructed, extended, implemented, or mixed in. They let library authors express intent and enforce contracts across package boundaries.
base: Can be extended but not implemented outside its library, so subtypes always inherit the real implementation. Guarantees every instance is a genuine subtype.
interface: Can be implemented but not extended outside its library. Used to define a pure contract while keeping the implementation private.
final: Can neither be extended nor implemented outside its library: fully closed. Lets you evolve the class freely without breaking downstream code.
sealed: Implicitly abstract and final; all direct subtypes must live in the same library. Enables exhaustive switch checks since the compiler knows every subtype.
Default (no modifier): A plain class can still be constructed, extended, and implemented as before, preserving backward compatibility.
Q36.What is a factory constructor, and how does it differ from a generative constructor?
factory constructor, and how does it differ from a generative constructor?A generative constructor always creates a brand-new instance and initializes its fields directly; a factory constructor is a method-like constructor that decides what to return, so it can return a cached instance, a subtype, or a computed object.
Generative constructor:
Runs the normal object-creation flow: allocates a new instance and sets its fields (can be default, named, or redirecting).
Can appear in an initializer list and call super.
Factory constructor:
Declared with factory and must return an instance; it does not implicitly create one.
Common uses: singletons/caching, returning a subtype, or parsing (e.g. fromJson).
Has no access to this and cannot be used in an initializer list.
Q37.What is the difference between a Mixin and an Extension method? When would you use 'on' in a mixin definition?
on' in a mixin definition?A mixin injects shared behavior and state into a class's type hierarchy (it becomes part of the object's supertype chain), while an extension adds methods to an existing type without subclassing or modifying it. Use on in a mixin to restrict which classes may use it, giving the mixin access to that type's members.
Mixin:
Applied with with; contributes real methods and fields, participates in is checks, and can be overridden.
Reuses behavior across an inheritance hierarchy.
Extension method:
Adds functionality to a type you may not own (e.g. String), resolved statically at compile time.
Not part of the type's identity: no new subtype, no polymorphic override.
When to use on: Constrains the mixin to subtypes of a given class so it can call that class's members safely.
Q38.Explain the difference between Records and Patterns in Dart 3. When would you use a Record instead of a custom class?
Records are a lightweight, immutable data type that bundle multiple values (positional and/or named) without declaring a class; patterns are a syntax for destructuring and matching values, including records. They complement each other: records package data, patterns take it apart.
Records:
Anonymous, immutable, value-equality bundles: (int, String) or ({int id, String name}).
Great for returning multiple values from a function without a wrapper class.
Patterns: Destructure and match: var (a, b) = record; or in switch cases with guards.
Record vs custom class:
Use a record for ad-hoc, throwaway groupings and multiple return values where naming a type adds no value.
Use a class when you need a named type, methods, named constructors, validation, or when the shape is part of your domain/API.
Q39.What is the difference between with, implements, and extends? Can a mixin have a constructor? Why or why not?
with, implements, and extends? Can a mixin have a constructor? Why or why not?These three keywords express different relationships: extends inherits implementation from a superclass, implements adopts a type's interface but must supply every member, and with mixes reusable behavior into a class. A mixin cannot have a generative constructor because it isn't instantiated on its own and doesn't sit in a fixed constructor chain.
extends: Single inheritance: reuses the parent's implementation and can call super.
implements: Inherits only the contract; you must implement all members. Allows multiple interfaces.
with: Applies one or more mixins, folding their methods/fields into the class linearization.
Why no mixin constructor:
A mixin is applied to many host classes and has no independent creation point, so it can't run constructor logic or pass arguments.
It may declare fields; initialize them in the host constructor or via late/setup methods. (A mixin class can have an unnamed generative constructor only when used as a normal class.)
Q40.What are Dart Records and Pattern Matching, and how do they change how we handle data structures in Dart 3?
Records are immutable value bundles of multiple fields, and pattern matching lets you destructure and branch on the shape of data. Together they make Dart handle structured data more declaratively: less boilerplate, safer branching, and cleaner multiple-return values.
Records:
Compact syntax (1, 'a') or named (x: 1, y: 2), with structural equality built in.
Eliminate throwaway wrapper classes for grouping data.
Pattern matching:
Destructures records, lists, maps, and objects in switch, if-case, and assignments.
Supports guards (when) and exhaustiveness checking with sealed types.
How it changes data handling:
Replaces manual index/field access and nested if chains with concise, readable matches.
Encourages a more functional, expression-oriented style.
Q41.What are sealed classes in Dart 3, and how do they improve state management compared to standard abstract classes?
sealed classes in Dart 3, and how do they improve state management compared to standard abstract classes?A sealed class is an abstract class whose complete set of direct subtypes is known at compile time (all defined in the same library). This lets the compiler enforce exhaustive switch handling, which is ideal for modeling a fixed set of states.
What sealed gives you:
Implicitly abstract and final: cannot be instantiated or subtyped outside the library.
The compiler knows every subtype, enabling exhaustiveness checks.
Why it beats a plain abstract class for state:
With a plain abstract class, the compiler can't know all subtypes, so a switch needs a default and won't warn when you add a new state.
With sealed, forgetting to handle a new state is a compile-time error, catching bugs early.
Fits state management well: Model UI states (Loading, Success, Error) as subtypes and switch exhaustively in the view.
Q42.What are extension methods in Dart, and what problems do they solve compared to writing utility/helper functions?
Extension methods let you add new methods, getters, and operators to an existing type (even ones you don't own, like String or int) without subclassing or changing the original. They give utility code natural, discoverable call-site syntax.
Problems they solve:
Readability: 'abc'.capitalize() reads better than capitalize('abc').
Discoverability: methods show up in IDE autocomplete on the receiver type.
Cohesion: group related helpers on the type they operate on instead of scattered global functions.
How they differ from utility functions:
Resolved statically at compile time based on the static type: they are not polymorphic and don't override real methods.
Must be imported to be in scope; can be named to resolve conflicts.
Limitations: Cannot add instance fields or override existing members; work off the static, not runtime, type.
Q43.What are named constructors in Dart, and how do they differ from factory constructors?
Named constructors give a class multiple, clearly-labeled ways to create instances, while factory constructors add control over what instance gets returned (they don't have to create a fresh one).
Named constructors:
Syntax like ClassName.identifier(...): just an alternative initializer with a descriptive name.
Must always create and return a new instance of the class; they run initializer lists and the body normally.
Examples: Point.origin(), DateTime.now().
Factory constructors:
Declared with the factory keyword; they must return an instance explicitly.
Can return a cached instance, a subtype, or run logic before deciding what to build (e.g. int.parse style parsing or singletons).
No access to this and no initializer list, since an object isn't guaranteed to be freshly created.
Key difference:
Named = a labeled way to construct a new object.
Factory = full control over instance creation (may reuse or substitute objects).
Q44.Why does Flutter favor composition over inheritance? Give an example of how this manifests in the widget library.
Flutter favors composition because you build complex UI by nesting many small, single-purpose widgets rather than subclassing a large base class and overriding behavior. This keeps widgets simple, reusable, and easy to reason about.
Why composition wins here:
Deep inheritance hierarchies are rigid and hard to extend; a widget would need to know every possible variation.
Composition lets you combine independent widgets to get new behavior without modifying any of them.
How it manifests in the widget library:
Instead of a PaddedCenteredText class, you wrap: Padding holds a Center holds a Text.
Layout, styling, and behavior are separate widgets (Align, Opacity, GestureDetector) you stack as needed.
Result: small widgets, each doing one thing, assembled into trees.
Q45.Why is immutability emphasized in Flutter widgets, and how does Dart help you enforce it?
Widgets are immutable because Flutter treats them as cheap, throwaway descriptions of the UI at a moment in time: you don't mutate a widget, you build a new one. Immutability makes rebuilds predictable and diffing reliable.
Why immutability matters:
Flutter rebuilds widget trees frequently; if widgets could mutate, comparing old vs new configuration would be unsafe.
Mutable state lives elsewhere (in State objects or external stores), keeping the widget itself a pure snapshot.
Enables the const optimization and safe reuse across rebuilds.
How Dart enforces it:
Widget fields are declared final, so they can't be reassigned after construction.
const constructors require all fields be compile-time constants, guaranteeing deep immutability.
The convention: all data flows in through the constructor, none is mutated afterward.
Q46.Walk through the lifecycle of a StatefulWidget. What is the significance of didUpdateWidget and didChangeDependencies?
StatefulWidget. What is the significance of didUpdateWidget and didChangeDependencies?A StatefulWidget's logic lives in its State object, which Flutter drives through a defined lifecycle: creation, dependency wiring, building, updating, and disposal. didUpdateWidget and didChangeDependencies are the hooks that let you react to external changes without recreating the State.
createState(): Flutter calls this once to create the State instance.
initState(): called once after insertion; initialize controllers, subscriptions, animations. Cannot use context for inherited widgets safely yet.
didChangeDependencies(): called right after initState and again whenever an InheritedWidget this State depends on changes.
build(): produces the widget tree; called often, so keep it pure and cheap.
didUpdateWidget(oldWidget): called when the parent rebuilds and supplies a new widget instance of the same type; compare old vs new config and update accordingly.
dispose(): called once when removed permanently; release controllers and subscriptions here.
Significance of didUpdateWidget: The State persists but the widget (its config) is immutable and replaced. This hook lets you resync internal state to new props, e.g. re-subscribe if a stream or controller passed in changed.
Significance of didChangeDependencies: Lets you respond to inherited data (e.g. Theme.of(context), MediaQuery, a Provider) and safely do context-dependent init that initState can't.
Q47.If a StatelessWidget is immutable, how can it still be 'rebuilt'? What triggers the didUpdateWidget lifecycle method in a StatefulWidget?
StatelessWidget is immutable, how can it still be 'rebuilt'? What triggers the didUpdateWidget lifecycle method in a StatefulWidget?A StatelessWidget being immutable means a given instance never changes: instead, Flutter discards it and constructs a brand new instance when the parent rebuilds. "Rebuilding" is really replacement plus re-running build(). A StatefulWidget behaves the same at the widget layer, but its State object survives, which is why didUpdateWidget exists.
Why an immutable widget can rebuild:
The widget is just a lightweight description. When its parent rebuilds, a new instance is created and Flutter reconciles it against the Element tree, calling build() again.
Immutability applies to a single instance's fields, not to the tree's ability to swap instances.
What triggers didUpdateWidget:
The parent rebuilds and produces a new widget of the same runtimeType and key at the same position, so Flutter reuses the existing Element and State, then calls didUpdateWidget with the old widget.
It is not triggered by setState (that just calls build), nor when the type/key differs (that recreates the State).
Q48.Explain the difference between didUpdateWidget() and didChangeDependencies(). In what specific scenarios is each triggered?
didUpdateWidget() and didChangeDependencies(). In what specific scenarios is each triggered?Both react to change without recreating the State, but didUpdateWidget fires when the parent passes new configuration (props), while didChangeDependencies fires when an inherited dependency this State reads has changed.
didUpdateWidget(oldWidget):
Triggered when the parent rebuilds and gives a new widget instance of the same type/key.
Use it to compare oldWidget to widget and react to changed props: e.g. the parent swapped in a new animationDuration or a different callback/URL to fetch.
didChangeDependencies():
Triggered once after initState, and again whenever an InheritedWidget obtained via context.dependOnInheritedWidgetOfExactType changes.
Use it for context-dependent setup: reading Theme, MediaQuery, Localizations, or a Provider value, and re-running work when those change.
Quick contrast: Props changed from above the widget: didUpdateWidget. Inherited/ambient data changed: didChangeDependencies.
Q49.How do you observe the app lifecycle (resumed, paused, inactive, detached) in Flutter, and why would you need to?
You observe app lifecycle by registering a WidgetsBindingObserver and overriding didChangeAppLifecycleState, which reports transitions like resumed, inactive, paused, and detached. You need it to save work, pause activity, or refresh when the app moves between foreground and background.
How to observe:
Mix WidgetsBindingObserver into a State.
Register with WidgetsBinding.instance.addObserver(this) in initState and remove it in dispose.
Override didChangeAppLifecycleState(AppLifecycleState state).
The states:
resumed: visible and receiving input (foreground).
inactive: in transition or partially obscured (e.g. incoming call, app switcher); no input.
paused: backgrounded, not visible, still running.
detached: engine running but no view attached (being torn down).
Why you need it:
Pause/resume video, animations, or sensors to save battery.
Persist unsaved data or stop timers when going to background.
Refresh data or re-authenticate on resume.
Q50.What are the fundamental architectural differences between Provider and Riverpod, and why is Riverpod often preferred for its compile-time safety?
Provider and Riverpod, and why is Riverpod often preferred for its compile-time safety?Provider is a wrapper around InheritedWidget that locates dependencies by type through the widget tree, while Riverpod moves state out of the tree into globally-declared providers resolved by a container, giving it compile-time safety Provider can't offer.
Provider is tree-coupled:
A provider must sit above its consumers in the widget tree, and lookup is by type via BuildContext.
Reading a type that isn't provided throws a runtime ProviderNotFoundException, and two providers of the same type collide.
Riverpod is tree-independent:
Providers are top-level objects, not widgets, held in a ProviderContainer (exposed to the UI by ProviderScope).
Access is by reference to the provider object, not by type, so identity is unambiguous.
Why compile-time safety:
Because you reference the actual provider variable, a missing or wrong dependency is a compile error, not a runtime crash.
No context needed to read, so providers compose and depend on each other safely with ref.watch.
Practical wins:
Easy overriding for tests and scoping via overrides.
Auto-disposal, family parameters, and combining providers without ProxyProvider gymnastics.
Q51.Explain the core philosophy of the BLoC pattern. What are the trade-offs of using BLoC versus a simpler solution like ChangeNotifier?
BLoC pattern. What are the trade-offs of using BLoC versus a simpler solution like ChangeNotifier?BLoC's core philosophy is to separate business logic from the UI by having the UI send events and the logic emit an immutable stream of states, giving a unidirectional, predictable, testable flow. Versus ChangeNotifier, it trades more boilerplate for stronger structure and traceability.
Core philosophy:
UI is dumb: it dispatches events and rebuilds from emitted states, never holding logic.
Unidirectional data flow makes state transitions explicit and reproducible.
States are immutable, so every change is a new, comparable object.
BLoC trade-offs (vs ChangeNotifier):
Pros: predictable, easy to test, strong separation, and an implicit audit log of events.
Cons: significant boilerplate (event/state classes) and a steeper learning curve.
ChangeNotifier trade-offs:
Pros: minimal code, built into Flutter, quick for simple/local state.
Cons: mutable state and notifyListeners() calls scattered around blur the line between UI and logic, harder to trace at scale.
Rule of thumb: reach for BLoC when logic is complex or shared across teams; ChangeNotifier (or Cubit) when it isn't.
Q52.What is the Provider package, and how does it relate to InheritedWidget?
Provider package, and how does it relate to InheritedWidget?Provider is a popular state-management package that is essentially a friendly, less error-prone wrapper around Flutter's InheritedWidget, making it easy to expose objects down the widget tree and rebuild only the widgets that depend on them.
What InheritedWidget provides:
A low-level Flutter primitive that propagates data down the tree and lets descendants subscribe via dependOnInheritedWidgetOfExactType.
Writing it by hand is verbose and easy to get wrong (update logic, generics).
What Provider adds:
Simple API: Provider, ChangeNotifierProvider, context.watch, context.read.
Lifecycle handling: creation and disposal of the exposed object.
Granular rebuilds via Consumer and Selector.
Relationship summary:
Under the hood a Provider builds an InheritedWidget, so lookups are still by type and tied to the tree.
It's the ergonomic layer; the mechanism is still InheritedWidget.
Q53.Explain the 'Event-in, State-out' philosophy of the BLoC pattern. When would you choose a Cubit over a full Bloc?
BLoC pattern. When would you choose a Cubit over a full Bloc?"Event-in, State-out" means a Bloc receives events as input and emits states as output, keeping a strict unidirectional flow. Choose a Cubit when you don't need that event layer: it exposes methods that emit states directly, trading traceability for simplicity.
The philosophy:
UI dispatches an event (add(Event)), the Bloc processes it and emits one or more states.
Everything flows one direction: input events, output states, never the reverse.
Because events are explicit objects, you get a natural log of everything that happened.
Bloc vs Cubit:
Bloc: event-driven, more boilerplate, best when you need traceability, complex transitions, or transformations (debounce, throttle) on incoming events.
Cubit: method-driven, calls emit(state) directly, less code, best for straightforward state changes.
When to pick Cubit:
Logic is simple and you don't need an event audit trail.
You want to reduce boilerplate; you can always refactor to a full Bloc later if complexity grows.
Q54.What are ValueNotifier and ValueListenableBuilder, and when would you prefer them over a full ChangeNotifier or a state-management package?
ValueNotifier and ValueListenableBuilder, and when would you prefer them over a full ChangeNotifier or a state-management package?A ValueNotifier is a tiny ChangeNotifier that holds a single value and notifies listeners when that value changes; ValueListenableBuilder rebuilds only its child subtree in response. Prefer them for small, localized, single-value state where a full class or a package would be overkill.
ValueNotifier<T>:
Wraps one value in .value; setting it (to a non-equal value) fires notifications automatically.
Less boilerplate than writing a custom ChangeNotifier with fields and notifyListeners().
ValueListenableBuilder:
Listens to a ValueListenable and rebuilds only its builder, not the whole widget.
Use the child param for subtrees that don't depend on the value to skip rebuilding them.
When to prefer them:
Single, self-contained value: a counter, toggle, form field, loading flag.
No cross-widget dependency graph and no need for DI or derived/computed state.
When to reach for more:
Multiple related fields or complex transitions: a full ChangeNotifier (with Provider) is clearer.
App-wide shared state, testability, and DI: a package like Riverpod or Bloc.
Q55.How does GetX approach state management, routing, and dependency injection, and what are the criticisms of using it?
GetX approach state management, routing, and dependency injection, and what are the criticisms of using it?GetX is an all-in-one package bundling reactive state management, navigation without context, and a service locator for DI, aiming for minimal boilerplate. Its convenience is also the root of most criticism: it hides Flutter idioms and encourages tight coupling.
State management:
Reactive: wrap values as .obs and rebuild with Obx(() => ...) that auto-tracks used observables.
Simple: GetBuilder with manual update() calls for lower overhead.
Routing: Context-free navigation via Get.to(), Get.back(), named routes, and dialogs/snackbars from anywhere.
Dependency injection: Register with Get.put() / Get.lazyPut() and retrieve with Get.find(); controllers can be auto-disposed by route bindings.
Criticisms:
Hides framework concepts (context, InheritedWidget), so devs learn GetX instead of Flutter.
Global service-locator access encourages tight coupling and hard-to-trace state.
Does too many unrelated things, making testing and refactoring harder as apps grow.
"Magic" implicit disposal and reactivity can cause subtle bugs.
Q56.Why is the Repository Pattern used in Flutter, and how does it help in switching between a local database and a remote API?
The Repository Pattern puts a single abstraction between your business logic and the concrete data sources, so the rest of the app requests data without knowing whether it comes from a remote API, a local cache, or both. This decoupling is exactly what makes swapping or combining sources painless.
What it is: An abstract interface (e.g. UserRepository) describing operations like getUser(), implemented concretely in the data layer.
Why it's used:
Single source of truth: callers depend on the contract, not on Dio, Hive, or SQLite.
Testability: inject a fake repository to unit-test logic without a network or DB.
Centralizes caching, error handling, and mapping DTOs to domain entities.
Switching local vs remote:
The implementation decides: try remote, fall back to local cache when offline, then persist.
Consumers never change because the interface stays the same; only wiring/DI changes.
Q57.Explain the concept of Dependency Injection in Flutter and how tools like get_it or Riverpod handle it.
get_it or Riverpod handle it.Dependency Injection means an object receives its dependencies from the outside instead of creating them itself, which decouples classes and makes them testable. In Flutter this ranges from simple constructor injection to service locators like get_it and provider-based systems like Riverpod.
Why DI matters:
Swap real implementations for mocks/fakes in tests.
Decouples construction from usage, honoring dependency inversion.
get_it (service locator):
Register once (registerSingleton, registerLazySingleton, registerFactory) and resolve globally with getIt<T>().
No BuildContext needed; works anywhere, including non-widget code.
Riverpod:
Dependencies are declared as providers; consumers read them with ref.watch / ref.read.
Compile-time safe, reactive, and easily overridden in tests via ProviderScope overrides.
Key distinction: get_it is pure lookup; Riverpod adds reactivity and scoped lifecycle to DI.
Q58.Compare Get_it (Service Locator) vs. Provider/Riverpod for dependency injection. What are the trade-offs of each approach?
Get_it (Service Locator) vs. Provider/Riverpod for dependency injection. What are the trade-offs of each approach?Both provide dependencies, but get_it is a global service locator you pull from imperatively, while Provider/Riverpod inject through the widget tree (Riverpod without needing context) and add reactivity. The trade-off is simplicity and global reach versus scoping, safety, and reactive rebuilds.
get_it (service locator):
Pros: dead simple, no context, accessible from anywhere, great for wiring repositories/services.
Cons: global access hides dependencies, no automatic UI rebuilds, lifecycle/scoping is manual.
Provider:
Pros: idiomatic, scoped to the widget tree, rebuilds listeners on change.
Cons: needs BuildContext; runtime errors if a provider isn't found (ProviderNotFoundException).
Riverpod:
Pros: compile-time safe, no context, reactive, easy test overrides, auto-dispose.
Cons: steeper learning curve and more concepts than a plain locator.
Choosing / combining:
Need reactive state tied to UI: Provider/Riverpod.
Need plain object wiring outside widgets: get_it.
A common pattern is get_it for the service/data layer and Riverpod for presentation state.
Q59.Explain the difference between a Future and a Stream. When would you use a Broadcast Stream over a Single-subscription Stream?
Future and a Stream. When would you use a Broadcast Stream over a Single-subscription Stream?A Future represents a single asynchronous value that completes once (with a result or an error). A Stream represents a sequence of asynchronous events over time (zero to many values, plus optional error and done). Think one delivery vs a pipe of deliveries.
Future = one value: Consumed with await or .then(); e.g. an HTTP response.
Stream = many values: Consumed with await for or .listen(); e.g. sensor updates, websocket messages, button clicks.
Single-subscription stream (default):
Allows exactly one listener for its lifetime; buffers events until listened to.
Best for a sequential result like reading a file in chunks.
Broadcast stream:
Allows many simultaneous listeners; does not buffer, so late listeners miss earlier events.
Use when multiple parts of the app must react to the same events (e.g. an app-wide event bus), created via .asBroadcastStream() or StreamController.broadcast().
Q60.How do FutureBuilder and StreamBuilder work, and how do you handle the different connection states (waiting, active, done, error) inside their builder?
FutureBuilder and StreamBuilder work, and how do you handle the different connection states (waiting, active, done, error) inside their builder?FutureBuilder and StreamBuilder are widgets that subscribe to a Future or Stream and rebuild their UI whenever the async source emits. Their builder callback receives an AsyncSnapshot, and you branch on its connection state and data/error to decide what to render.
How they work:
You give a future (or stream) plus a builder; the widget listens and calls builder(context, snapshot) on each update.
FutureBuilder fires once (single value); StreamBuilder rebuilds on every event.
Reading the snapshot:
snapshot.connectionState == ConnectionState.waiting: no data yet, show a spinner.
snapshot.hasError: show an error UI (check this before data).
snapshot.hasData: render the result.
ConnectionState.active (streams) means connected and receiving; done means the source is closed.
Common pitfall: Don't create the future inline in build(): every rebuild makes a new one and re-runs the work. Create it in initState (or a state holder).
Q61.Explain the phrase: 'Constraints go down, sizes go up, parent sets position.' What happens if a child widget tries to be larger than its parent's constraints?
It describes Flutter's single-pass layout protocol. A parent passes BoxConstraints (min/max width and height) down to its child; the child chooses its own size within those constraints and returns it up; then the parent decides where to place the child. Layout information flows down as constraints and back up as sizes.
Constraints go down: Each widget receives a min/max width and height from its parent and cannot ignore them.
Sizes go up: The child picks a size that satisfies the constraints and reports it back to the parent.
Parent sets position: Given the child's size, the parent positions it (e.g. centering, aligning) but doesn't know the child's internal layout.
If a child wants to be bigger than allowed:
The constraints win: the child is clamped to the parent's maxWidth/maxHeight, so it simply can't exceed them.
If its content overflows the given box, you get the yellow-and-black overflow warning (content is painted outside/clipped), but the widget's reported size still obeys the constraints.
Limitation: A widget can't know its own size based on children and constraints alone in a way that lets it size itself outside the box; use LayoutBuilder to read incoming constraints when you need them.
Q62.What is a Sliver, and why would you use a CustomScrollView with Slivers instead of a standard ListView?
Sliver, and why would you use a CustomScrollView with Slivers instead of a standard ListView?A Sliver is a portion of a scrollable area that knows how to render itself lazily based on the current viewport, using a specialized layout protocol (SliverConstraints/SliverGeometry) instead of box constraints. You use a CustomScrollView with multiple slivers when you need several different scrolling behaviors to share one scroll position and one physics.
What a sliver is:
A scrollable model region: e.g. SliverList, SliverGrid, SliverAppBar, SliverToBoxAdapter.
It builds only the children currently visible (plus a small cache), which is efficient for long content.
Why CustomScrollView over ListView:
A ListView is itself effectively one sliver; a CustomScrollView hosts many slivers in a single scroll view.
Lets you mix effects impossible in a plain list, e.g. a collapsing SliverAppBar followed by a grid and then a list, all scrolling together.
Avoids nesting scrollables (which causes shared-scroll and performance headaches) by unifying them under one ScrollController.
Rule of thumb: A single uniform list: use ListView. Multiple heterogeneous, coordinated scrolling sections or custom scroll effects: reach for slivers.
Q63.How does the BoxConstraints model work, and what is the difference between tight and loose constraints?
BoxConstraints model work, and what is the difference between tight and loose constraints?BoxConstraints is the set of min/max width and height a parent passes down during layout; a child must choose a size within them. Constraints go down, sizes come back up, parent positions the child. "Tight" means min equals max (size is forced), "loose" means min is zero so the child can pick any size up to the max.
The four fields: minWidth, maxWidth, minHeight, maxHeight bound the child's chosen size.
Tight constraints: min == max on an axis, so the child has no choice (e.g. a full-screen widget under BoxConstraints.tight).
Loose constraints: min is 0, max is bounded, so the child sizes to its content up to the max (e.g. a Center gives its child loose constraints).
Why it matters: Explains errors like "unbounded height": a widget wanting to shrink-wrap gets an infinite max (e.g. a ListView inside a Column) and can't decide a size.
Q64.What is MediaQuery used for, and how does it differ from LayoutBuilder when building responsive layouts?
MediaQuery used for, and how does it differ from LayoutBuilder when building responsive layouts?MediaQuery exposes device/window-level info (screen size, orientation, padding, text scale), while LayoutBuilder gives the constraints of the specific parent widget. Use MediaQuery for global context and LayoutBuilder to make a widget respond to the actual space it is given.
MediaQuery:
Access via MediaQuery.of(context).size, orientation, padding (notches, status bar), viewInsets (keyboard).
Reflects the whole screen, not the widget's slice of it.
LayoutBuilder:
Its builder receives BoxConstraints from the parent, so it knows how much space this widget actually has.
Better for reusable components inside split views, panels, or cards where screen size isn't the true available space.
Rule of thumb:
Global breakpoints, orientation, safe areas: MediaQuery.
Local, container-relative decisions: LayoutBuilder.
Q65.Explain how Declarative Routing works in Flutter, for example using go_router.
go_router.Declarative routing means you describe what the navigation stack should look like for a given app state, and the router builds it: instead of imperatively calling push/pop, you map URLs/state to pages. go_router sits on top of Navigator 2.0 and does this cleanly.
Imperative vs declarative:
Navigator 1.0 is imperative: you mutate the stack directly with Navigator.push.
Declarative: the current route stack is a function of app state, so you rebuild routes when state changes.
How go_router works:
You define a list of GoRoute entries mapping path patterns (/user/:id) to a builder.
Navigate with context.go('/user/42') (replace stack) or context.push (add on top).
It parses URLs, extracts path/query params, and drives the underlying Router widget.
Key benefits:
URL-based, so web browser back/forward and deep links work naturally.
Centralized route table plus redirect for auth guards (redirect to /login when logged out).
Nested routes and ShellRoute for persistent UI like bottom nav bars.
Why prefer it over raw Navigator 2.0: the raw API (RouterDelegate, RouteInformationParser) is powerful but very verbose; go_router gives the declarative model with far less boilerplate.
Q66.How do you handle deep linking in a Flutter application?
Deep linking lets an external URL (web link, notification, custom scheme) open a specific screen in your app. In Flutter you configure the platform to route the link to the app, then let a router (typically go_router / Navigator 2.0) parse the path and build the matching route stack.
Two link types:
Custom scheme (myapp://user/42): simple, but not verified by the OS.
Universal/App Links (https://example.com/user/42): verified via domain association files, more secure and preferred.
Platform configuration:
Android: declare an intent-filter in AndroidManifest.xml and host assetlinks.json for App Links.
iOS: set up Associated Domains and host apple-app-site-association.
Handling in Flutter:
With go_router, the incoming URI is fed to the router's parser, which matches a GoRoute and builds that screen automatically.
Distinguish cold start (app launched by link) from resumed (already running): the Router handles both via RouteInformationProvider.
For manual control you can use packages like app_links / uni_links to receive the raw URI stream.
Enable Flutter's built-in support: set flutter_deeplinking_enabled and ensure the router reconstructs a sensible back stack, not just the leaf screen.
Q67.How do named routes and onGenerateRoute work in Navigator 1.0, and how do you pass and validate arguments?
onGenerateRoute work in Navigator 1.0, and how do you pass and validate arguments?Named routes are string identifiers registered in MaterialApp.routes; you navigate with Navigator.pushNamed. onGenerateRoute is the fallback/factory called when a route isn't in that static map, letting you build routes dynamically and, crucially, parse and validate arguments.
Static named routes:
Defined as a Map<String, WidgetBuilder> in routes:.
Good for simple, static routes with no argument parsing.
Passing arguments:
Pass via the arguments parameter: pushNamed('/detail', arguments: id).
Read them with ModalRoute.of(context)!.settings.arguments.
onGenerateRoute:
Receives a RouteSettings with name and arguments; you return a Route (e.g. MaterialPageRoute).
This is the right place to validate: check the argument type/shape and return a 404/error page if invalid, avoiding unsafe casts.
Centralizes route logic and gives type safety at one point.
Related hook: onUnknownRoute handles names nothing else matched.
Q68.How do MethodChannels facilitate communication between Dart and Native code? Is the communication synchronous or asynchronous?
MethodChannels facilitate communication between Dart and Native code? Is the communication synchronous or asynchronous?A MethodChannel lets Dart invoke named methods on the native side (and vice versa) by sending serialized messages over a named channel. The communication is always asynchronous: invokeMethod returns a Future.
How a call flows:
Dart calls channel.invokeMethod('name', args); args are serialized by a codec (default StandardMethodCodec).
The message crosses to the native handler (MethodCallHandler on Android, setMethodCallHandler on iOS), which returns a result, error, or notImplemented.
Always asynchronous:
You await the Future; the call never blocks the UI isolate.
Messages are passed on the platform thread; the codec handles marshaling both ways.
Bidirectional: Native can also invoke methods back on Dart over the same channel name.
Q69.Why is ListView.builder more performant than a standard ListView? What is the 'viewport' and how does Flutter handle garbage collection of widgets that scroll off-screen?
ListView.builder more performant than a standard ListView? What is the 'viewport' and how does Flutter handle garbage collection of widgets that scroll off-screen?A standard ListView builds all its children up front, while ListView.builder lazily builds children on demand only as they enter the visible area (the viewport). For long or infinite lists this means constant work instead of work proportional to the whole list.
Standard ListView (with a children list): Instantiates every child widget immediately, even off-screen ones, so 10,000 items builds 10,000 widgets.
ListView.builder: Takes an itemBuilder callback and calls it lazily, building only what fits in the viewport plus a small cache area.
The viewport: The visible scrollable window. Flutter renders items in it plus a cacheExtent buffer just beyond the edges for smooth scrolling.
What happens off-screen (not literal GC):
As items scroll out, their Element/RenderObject are recycled or destroyed and removed from the render tree, freeing them for Dart's garbage collector.
When they scroll back in, itemBuilder rebuilds them, so keep item state elsewhere (e.g. in a model) rather than in the widget.
Q70.How do const constructors and RepaintBoundary help in optimizing a Flutter app's performance?
const constructors and RepaintBoundary help in optimizing a Flutter app's performance?They optimize two different phases: const constructors reduce work in the build/rebuild phase by making widgets cacheable and skippable, while RepaintBoundary reduces work in the paint phase by isolating repaints to a subtree.
const constructors (build-time):
A const widget is canonicalized at compile time, so the same instance is reused instead of reallocated.
During a parent rebuild, Flutter sees the identical const widget and short-circuits, skipping that subtree's rebuild entirely.
RepaintBoundary (paint-time):
Puts a subtree on its own layer so its repaints don't invalidate neighbors, and neighbors' repaints don't invalidate it.
Best around a small area that repaints often next to expensive static content.
Together: const cuts rebuild count; RepaintBoundary cuts repaint area. Both are targeted, so measure with DevTools rather than applying blindly.
Q71.How would you cache and efficiently load images in Flutter, and what does the framework do out of the box?
For network images, Image.network already caches decoded images in memory via Flutter's ImageCache; for disk caching, resilience, and placeholders you reach for a package like cached_network_image. The key is caching the decoded image at the size you actually display.
What Flutter gives you out of the box:
An in-memory ImageCache (default ~1000 images / 100MB) keyed by the ImageProvider, so repeated uses reuse the decoded bitmap.
HTTP-level caching only if the server sends cache headers; the memory cache is cleared on app restart.
Persistent disk caching: cached_network_image stores files on disk so images survive restarts, with placeholder and errorWidget support.
Decode at display size (the big win): Set cacheWidth/cacheHeight (or ResizeImage) so a large source isn't decoded at full resolution into memory.
Manage memory pressure: Tune PaintingBinding.instance.imageCache size, and precacheImage to warm images before they appear.
Q72.Explain the difference between JIT and AOT compilation. How does Flutter use both during the development lifecycle?
JIT and AOT compilation. How does Flutter use both during the development lifecycle?JIT (Just-In-Time) compiles Dart at runtime for fast iteration, while AOT (Ahead-Of-Time) compiles to native machine code before running for maximum performance. Flutter uses JIT in development and AOT for release builds.
JIT compilation:
Compiles code on the fly as the app runs, enabling sub-second hot reload.
Used in debug mode; slower runtime and larger footprint because the VM and compiler ship with the app.
AOT compilation:
Compiles Dart to native ARM/x64 machine code at build time.
Used in profile and release modes: fast startup, predictable performance, no JIT warm-up.
Development lifecycle:
During development you run JIT for productivity (hot reload/restart).
For shipping you compile AOT so users get a fast, optimized native binary.
Flutter web is different: it compiles Dart to JavaScript (or WebAssembly) instead of AOT native code.
Q73.What is 'Tree Shaking' in the context of a Flutter web or mobile build, and how does it affect the final app size?
Tree shaking is a dead-code elimination step performed by the compiler that removes any code (classes, functions, fields) not actually reachable from your app, shrinking the final binary.
How it works:
The AOT compiler (or dart2js on web) walks the call graph from main() and keeps only reachable symbols.
Unused code from your app or imported packages is dropped from the output.
Effect on app size:
You can import a large library but only pay for the parts you use.
Only happens in release/profile (AOT) builds, not debug (JIT ships everything).
Caveat: reflection or dynamic code (e.g. dart:mirrors) defeats tree shaking, which is why Flutter forbids it. Code reached only dynamically may be retained or must be constant-analyzable.
Q74.What is icon/font tree shaking, and how does it affect the final application binary size?
Icon/font tree shaking strips unused glyphs from icon fonts (like MaterialIcons) at build time, so the bundled font contains only the icons your app actually references, dramatically reducing the font asset size.
What it does:
Full MaterialIcons font is over a megabyte; most apps use only a handful of glyphs.
The build analyzes constant IconData usage and subsets the font to just those code points.
When it runs: Automatic in release/profile builds; you'll see a "Font asset ... tree-shaken" message showing the size reduction.
The catch:
It requires const, statically-known IconData. Building icons dynamically (non-const code points) breaks the analysis.
Disable with --no-tree-shake-icons if you truly need dynamic icons.
Q75.How does a Flutter build in debug mode differ from profile and release modes?
The three build modes trade developer productivity for runtime performance: debug uses JIT with full tooling, profile is a release-like AOT build with profiling enabled, and release is the fully optimized AOT build shipped to users.
Debug mode:
JIT compilation with hot reload, assertions enabled, and service extensions/observatory available.
Slower runtime and larger size; not representative of real performance.
Run with flutter run.
Profile mode:
AOT-compiled like release but keeps profiling/tracing hooks (DevTools timeline) enabled.
Used to measure real performance; hot reload is disabled.
Run with flutter run --profile.
Release mode:
AOT-compiled, assertions and debugging disabled, tree shaking applied, smallest and fastest.
What you ship to stores; build with flutter build.
Q76.What is the difference between a Unit Test, a Widget Test, and an Integration Test in Flutter?
They differ in scope and cost: unit tests verify a single function or class in isolation, widget tests verify a widget's UI and interaction in a headless environment, and integration tests verify a full app running on a real device or emulator.
Unit test:
Tests one method, function, or class with no Flutter widgets involved (business logic, models, repositories).
Fastest and most numerous; uses test package and dependencies are usually mocked.
Widget test (component test):
Renders a widget via tester.pumpWidget() and drives it with WidgetTester (tap, enterText, pump).
Runs in a simulated environment (no real device); finds widgets with find and asserts with matchers like findsOneWidget.
Integration test:
Runs the complete app on a real device or emulator using integration_test, testing full flows across screens.
Slowest and fewest; catches issues real users hit (navigation, plugins, performance).
Rule of thumb: follow the testing pyramid: many unit tests, fewer widget tests, a small number of integration tests.
Q77.What is the difference between a Widget Test and a Golden Test? When would you use a Mockito/Mocktail mock versus a real repository in an integration test?
Mockito/Mocktail mock versus a real repository in an integration test?A widget test asserts logical/behavioral facts (which widgets exist, state after a tap), while a golden test asserts exact pixel appearance by comparing a render to a stored reference image. Mock in integration tests when you want deterministic, fast tests of your own code; use the real repository when the point is to validate the actual integration.
Widget test:
Checks structure and behavior via finders and matchers (findsOneWidget, text present, callback fired).
Doesn't verify visual details like layout, color, or font rendering.
Golden test:
Uses matchesGoldenFile() to compare rendered pixels against a committed reference image.
Catches visual regressions but is brittle across platforms/font changes and needs regeneration when UI intentionally changes.
Mock vs real repository in integration tests:
Use a mock (mockito/mocktail) to make tests fast, deterministic, and free of network/DB flakiness when validating UI flow and state handling.
Use the real repository when the goal is to verify the actual wiring (real API contracts, serialization, plugin behavior), accepting slower and flakier runs.
Common approach: mock external boundaries (network) but keep your own layers real.
Q78.What are 'Golden Image' tests? What are the trade-offs of using them for UI verification across different operating systems?
Golden image tests (goldens) render a widget or screen and compare the output pixel-by-pixel against a stored reference PNG; any deviation fails the test. They're powerful for catching visual regressions but notoriously fragile across environments.
How they work:
You call expectLater(find.byType(...), matchesGoldenFile('x.png')) and generate baselines with --update-goldens.
CI then compares fresh renders to the committed image.
Benefits:
Catch subtle visual regressions (spacing, color, layout) that behavioral tests miss.
Document intended appearance as a reviewable artifact.
Cross-OS trade-offs:
Font rendering and anti-aliasing differ between macOS, Linux, and Windows, so a golden generated on one OS often fails on another.
Fix by generating goldens in a single canonical environment (e.g., a Docker image or the same CI runner) and never locally, or use tolerance-based comparison / packages like golden_toolkit.
They're brittle: any legitimate UI change requires regenerating and reviewing images, adding maintenance cost.
Q79.How do you mock dependencies in Flutter tests, for example using mockito or mocktail?
mockito or mocktail?You mock by depending on abstractions (interfaces) and injecting a fake implementation in tests: mockito generates mock classes via code generation, while mocktail needs no codegen and works cleanly with null safety.
mockito:
Annotate with @GenerateMocks([Repo]) and run build_runner to generate MockRepo.
Stub with when(...).thenReturn(...) / thenAnswer(...) and assert with verify(...).
mocktail: Just extend Mock; no generated files. Use registerFallbackValue for custom types passed to any().
Enable it with dependency injection: Pass dependencies into constructors so tests can substitute mocks (avoid hard-coded singletons).
Q80.How does InheritedWidget propagate data down the tree? How does it notify its descendants to rebuild, and what is the performance cost of using it directly?
InheritedWidget propagate data down the tree? How does it notify its descendants to rebuild, and what is the performance cost of using it directly?An InheritedWidget sits in the tree and lets descendants look it up by type in O(1) via dependOnInheritedWidgetOfExactType(); when it's rebuilt with new data, it notifies exactly those dependents that registered on it to rebuild.
Propagation down the tree:
Flutter maintains a map of inherited widgets per element, so a child calling of(context) finds the nearest ancestor of that type instantly without walking the whole tree.
Calling dependOnInheritedWidgetOfExactType both reads the widget and registers the caller as a dependent.
How it notifies rebuilds:
When the widget is rebuilt, Flutter calls updateShouldNotify(oldWidget); if it returns true, every registered dependent is marked dirty and rebuilt.
Non-dependent descendants are untouched.
Performance cost:
Lookup is cheap, but notification is coarse: all dependents rebuild whenever updateShouldNotify is true, even if they only care about one field.
A single large model behind one InheritedWidget causes broad rebuilds; split into multiple inherited widgets or use InheritedModel for aspect-based dependencies.
This is why solutions like Provider (built on InheritedWidget) add selectors to narrow rebuilds.
Q81.What exactly is a BuildContext, and why is it dangerous to pass it across an async gap (e.g., after an await)?
BuildContext, and why is it dangerous to pass it across an async gap (e.g., after an await)?A BuildContext is a handle to a widget's location in the element tree. It's dangerous across an async gap because after an await the widget may have been removed from the tree, making the context stale and using it a runtime error.
Why the gap is risky:
During the await, the user could navigate away or the widget could be disposed (unmounted).
Then calling Navigator.of(context) or ScaffoldMessenger.of(context) throws or references a defunct element.
How to guard it:
Check if (!context.mounted) return; immediately after the await before touching the context (State also exposes mounted).
Or capture what you need (e.g., a Navigator or ScaffoldMessenger reference) before the await.
The use_build_context_synchronously lint flags this exact mistake.
Q82.What are Keys in Flutter? Explain the difference between LocalKey (ValueKey, ObjectKey, UniqueKey) and GlobalKey.
LocalKey (ValueKey, ObjectKey, UniqueKey) and GlobalKey.Keys are identifiers Flutter uses to preserve widget state and identity when the widget tree is rebuilt or reordered: they help the framework decide whether an existing Element can be reused for a new widget instead of being discarded and recreated.
Why keys matter:
During reconciliation Flutter matches new widgets to old elements by runtime type and key; without keys it matches by position, which breaks when items in a list are reordered, added, or removed.
Most needed for lists of stateful widgets where state (scroll, animation, form input) must follow the right item.
LocalKey: unique only among siblings:
ValueKey: keyed by a simple value like an id or string (ValueKey(user.id)).
ObjectKey: keyed by object identity, useful when a value alone isn't unique but the object instance is.
UniqueKey: generates a key equal only to itself, so the widget is always treated as new (forces a rebuild/state reset).
GlobalKey: unique across the entire app:
Gives direct access to a widget's State, BuildContext, or render object (e.g. formKey.currentState).
Lets a widget's state be preserved even when it moves to a different place in the tree.
Expensive: use sparingly, mainly for Form, or when you must reach into a widget imperatively.
Rule of thumb: use LocalKey (usually ValueKey) for list items; reach for GlobalKey only when you need cross-tree identity or direct state access.
Q83.What is the difference between implicit animations (like AnimatedContainer) and explicit animations driven by an AnimationController?
AnimatedContainer) and explicit animations driven by an AnimationController?Implicit animations animate a value automatically when it changes and manage their own controller; explicit animations give you an AnimationController so you control timing, direction, and lifecycle by hand. Choose implicit for simple one-shot transitions, explicit for complex, repeating, or coordinated animations.
Implicit animations (AnimatedContainer, AnimatedOpacity, etc.):
You just give a new target value and a duration; the widget tweens from old to new on rebuild.
No controller to create or dispose: minimal boilerplate.
Best for fire-and-forget changes triggered by state (size, color, alignment).
Explicit animations (AnimationController + AnimatedBuilder/Transition widgets):
You drive the animation with .forward(), .reverse(), .repeat(), and read its value.
Needs a TickerProvider (mixin SingleTickerProviderStateMixin) and must be disposed in dispose().
Best for continuous, reversible, staggered, or gesture-linked animations.
Rule of thumb: reach for implicit first; switch to explicit only when you need fine-grained control over the animation's progress.
Q84.What is the difference between Material and Cupertino widgets, and how do you build an app that feels native on both iOS and Android?
Material and Cupertino widgets, and how do you build an app that feels native on both iOS and Android?Material widgets implement Google's Material Design (Android's look), while Cupertino widgets replicate Apple's iOS design language. To feel native on both, you either adapt widgets per platform or, more commonly, pick one consistent design system and let Flutter's platform-adaptive widgets adjust key details.
Material widgets: Rooted at MaterialApp with Scaffold, AppBar, ElevatedButton, ripple effects, and ThemeData.
Cupertino widgets: Rooted at CupertinoApp with CupertinoPageScaffold, CupertinoNavigationBar, CupertinoButton, and iOS-style sliding transitions.
Approaches to native feel:
Check the platform with Theme.of(context).platform or Platform.isIOS and render the matching widget.
Use built-in adaptive constructors like Switch.adaptive and showAdaptiveDialog that swap style per platform automatically.
Pragmatic option: ship a single Material design everywhere but set pageTransitionsTheme so iOS gets iOS-style transitions.
Trade-off: full platform-specific UIs feel most native but double the widget work; a shared design with a few adaptive touches is usually the practical middle ground.
Q85.How does ThemeData work, and how would you implement light/dark mode and access theme values from a widget?
ThemeData work, and how would you implement light/dark mode and access theme values from a widget?ThemeData is a central bundle of visual properties (colors, typography, shapes) that MaterialApp propagates down the tree; widgets read it via Theme.of(context). Light/dark mode is handled by supplying separate theme and darkTheme and a themeMode.
How ThemeData flows:
Defined once on MaterialApp and inherited via an inherited widget, so any descendant can read it without passing it manually.
Modern apps build it from a seed color with ColorScheme.fromSeed for consistent Material 3 palettes.
Implementing light/dark mode:
Provide theme: and darkTheme:, then set themeMode: to ThemeMode.system, .light, or .dark.
For a user toggle, store the chosen ThemeMode in state (e.g. a provider) and rebuild MaterialApp.
Reading theme values in a widget:
Use Theme.of(context).colorScheme.primary or Theme.of(context).textTheme.titleLarge instead of hard-coded values.
Because it's inherited, changing the theme automatically rebuilds dependents.
Q86.Why does the Flutter team emphasize using const for widgets? What happens to the Element tree when a const widget is encountered during a rebuild?
const for widgets? What happens to the Element tree when a const widget is encountered during a rebuild?A const widget is a canonicalized, compile-time-constant object: Flutter can skip rebuilding its subtree because the same instance is reused, saving allocation and diffing work.
Why the team emphasizes it:
A const constructor produces a single canonical instance: two identical const widgets are the same object in memory.
It avoids re-allocating widget objects on every rebuild, reducing GC pressure.
What happens in the Element tree during rebuild:
When a parent rebuilds, Flutter compares the new widget with the old one at each position.
For a const widget the new and old references are identical, so the reconciliation shortcuts: the Element keeps its existing configuration and Flutter skips rebuilding that subtree entirely.
No new Element or RenderObject work is done for that branch.
Caveat: A widget can only be const if all its inputs are compile-time constants; dynamic values break constness.
Q87.What is a Ticker, and how does it relate to the vsync parameter in an AnimationController? How does a TextEditingController maintain state even if its parent widget is a StatelessWidget?
Ticker, and how does it relate to the vsync parameter in an AnimationController? How does a TextEditingController maintain state even if its parent widget is a StatelessWidget?Q88.How does addPostFrameCallback work, and when would you use it instead of running code directly in build or initState?
addPostFrameCallback work, and when would you use it instead of running code directly in build or initState?Q89.Compare the BLoC pattern with Riverpod. What are the pros and cons of each?
BLoC pattern with Riverpod. What are the pros and cons of each?Q90.Why is ChangeNotifier often considered less pure than BLoC or Redux for large-scale applications?
ChangeNotifier often considered less pure than BLoC or Redux for large-scale applications?Q91.How does Riverpod's dependency tree differ from the Flutter Widget tree? Why can Riverpod providers be accessed outside of the UI?
Riverpod's dependency tree differ from the Flutter Widget tree? Why can Riverpod providers be accessed outside of the UI?Q92.How does the Redux pattern map onto a Flutter app, and what are its trade-offs compared to BLoC?
Redux pattern map onto a Flutter app, and what are its trade-offs compared to BLoC?Q93.Explain Clean Architecture in the context of a Flutter project. How do you separate Data, Domain, and Presentation layers?
Q94.What is the MVVM pattern, and how does it map onto Flutter's widget and state-management concepts?
Q95.How does Dart's single-threaded event loop work? Explain the difference between the Microtask queue and the Event queue.
Microtask queue and the Event queue.Q96.Since Dart is single-threaded, how do Isolates achieve true parallelism? How do they communicate with the main thread, and when would you use an Isolate instead of a simple Future?
Isolates achieve true parallelism? How do they communicate with the main thread, and when would you use an Isolate instead of a simple Future?Q97.What is an Isolate, and how does it differ from a thread in Java or C++? How do two Isolates communicate with each other?
Isolate, and how does it differ from a thread in Java or C++? How do two Isolates communicate with each other?Q98.How would you build an adaptive/responsive UI in Flutter that works across phones, tablets, and desktop?
Q99.What is the difference between the imperative (Navigator 1.0) and declarative (Navigator 2.0) routing models? Why is Navigator 2.0 preferred for web and deep linking?
Navigator 1.0) and declarative (Navigator 2.0) routing models? Why is Navigator 2.0 preferred for web and deep linking?Q100.Explain the relationship between the Widget Tree, Element Tree, and RenderObject Tree. Why does Flutter maintain three separate trees instead of just one?
Widget Tree, Element Tree, and RenderObject Tree. Why does Flutter maintain three separate trees instead of just one?Q101.How does Flutter decide whether to update an existing Element or create a new one when a Widget's state changes?
Element or create a new one when a Widget's state changes?Q102.Describe the steps in the Flutter frame pipeline: Animate, Build, Layout, Paint, Composite.
Q103.What is an Element in Flutter, and why does Flutter use it instead of just regenerating the RenderObject tree every time?
Element in Flutter, and why does Flutter use it instead of just regenerating the RenderObject tree every time?Q104.What is a RenderObject, and when would you ever need to create a custom one?
RenderObject, and when would you ever need to create a custom one?Q105.Explain the layered architecture of Flutter: framework, engine, and embedder. What is each responsible for?
Q106.Explain how MethodChannel and EventChannel differ, and how data serialization works when passing objects between Dart and Native.
MethodChannel and EventChannel differ, and how data serialization works when passing objects between Dart and Native.Q107.What is Dart FFI, and in what scenario would you choose it over a standard Platform Channel?
Dart FFI, and in what scenario would you choose it over a standard Platform Channel?Q108.What is BasicMessageChannel, and how does it differ from MethodChannel and EventChannel?
BasicMessageChannel, and how does it differ from MethodChannel and EventChannel?Q109.What is Impeller, and why did the Flutter team move away from Skia for iOS and later Android? What specific performance issue like shader compilation jank does it solve?
Impeller, and why did the Flutter team move away from Skia for iOS and later Android? What specific performance issue like shader compilation jank does it solve?Q110.What is 'jank' in a mobile app, and how do you use the Flutter DevTools to identify if the bottleneck is on the UI thread or the Raster thread?
Q111.What is a RepaintBoundary, and how do you decide where to place one to optimize a complex animation?
RepaintBoundary, and how do you decide where to place one to optimize a complex animation?Q112.What is the significance of WebAssembly (WASM) support in Flutter Web, and how does it affect performance compared to the JavaScript/CanvasKit backend?
WASM) support in Flutter Web, and how does it affect performance compared to the JavaScript/CanvasKit backend?Q113.What concrete techniques do you use to minimize unnecessary widget rebuilds in a large screen?
Q114.Why is using IntrinsicHeight or IntrinsicWidth considered 'expensive' in terms of performance?
IntrinsicHeight or IntrinsicWidth considered 'expensive' in terms of performance?Q115.How do you handle State Restoration so a user returns to the exact same scroll position and form data after the OS kills the process?
Q116.When is a GlobalKey necessary versus a ValueKey or UniqueKey? What are the performance implications of using GlobalKey?
GlobalKey necessary versus a ValueKey or UniqueKey? What are the performance implications of using GlobalKey?