Top 30 Intermediate Flutter & Dart Interview Questions and Answers
30 intermediate multiple-choice Flutter & Dart interview questions, past the definitions: how the pieces fit together, what breaks in practice, and the trade-off behind a choice. They come from 30 bites in the Flutter & Dart library, the middle slice of the 138 Flutter & Dart interview questions in the library. Answer them here or read straight down. Every question carries the correct option, why it is correct, and a link to the bite it came from.
Flutter framework, Dart language, packages, Impeller
30 questions. Pick an answer, or open “Show the answer” to read it.
Answers are graded in your browser. Nothing is saved, and no XP or streak is earned here. The app keeps score.
Question 1 of 30
When accessing a property on a nullable object in Dart, what is the fundamental difference between using ?. and !?
Show the answer
Answer: c · ?. evaluates to null when the receiver is null, while ! casts to non-nullable and may throw at runtime.
The null-aware operator ?. short-circuits and yields null if the receiver is null, whereas ! forcibly casts away nullability and throws a runtime exception if the value is actually null. Option D is wrong because it confuses ?. with the ?? default-value operator and incorrectly suggests ! is compile-time safe.
Read the full bite: Describe Dart's null-aware ?. and null assertion ! operators
Question 2 of 30
A class declares late final UserService repo and assigns it in initState. What happens if repo is read in build before that assignment?
Show the answer
Answer: a · A LateInitializationError is thrown at the point of access
late shifts definite-assignment checks from compile time to runtime, so accessing the field before assignment throws LateInitializationError. Distractor C is tempting because that is exactly the compile-time error you would receive if the field were not marked late.
Question 3 of 30
Given List<int> nums = [1, 2, 3, 4] and bool includeZero = true, which literal produces [0, 2, 4]?
Show the answer
Answer: a · [if (includeZero) 0, for (var n in nums) if (n.isEven) n]
Option A correctly uses collection-if to prepend 0 and collection-for with a nested collection-if to filter evens inside one literal. Option B is tempting because where looks declarative, but without the spread operator it inserts the Iterable object itself as a single element rather than flattening its contents.
Read the full bite: How do you use collection-if and collection-for to declaratively build a list?
Question 4 of 30
Which statement accurately describes how Dart closures handle captured variables from an enclosing function?
Show the answer
Answer: d · They capture the variable binding by reference in a heap-allocated environment
Dart closures capture the variable binding by reference in the heap, so the inner function retains access even after the outer function returns and its stack frame is popped. This makes B incorrect because the enclosing function's stack frame does not remain on the call stack after it returns.
Read the full bite: What is a Dart closure? Write a function that returns a function.
Question 5 of 30
Which statement accurately contrasts Dart's extends, implements, and with keywords for class reuse?
Show the answer
Answer: a · extends inherits from one superclass; implements requires reimplementing every public member; with injects behavior without subtyping.
Option A is correct because extends provides single inheritance of implementation, implements forces the class to reimplement every public member itself, and with injects reusable behavior without creating an is-a relationship. Option B is wrong because extends does not allow multiple superclasses, implements does not reuse parent code, and with does not establish an is-a inheritance chain.
Read the full bite: What are the key differences between Dart extends, implements, and with?
Question 6 of 30
Which statement accurately describes a semantic difference between a Dart factory constructor and a generative constructor?
Show the answer
Answer: c · A factory constructor can return an existing cached instance or a subtype, while a generative constructor always produces a fresh instance of the exact class.
A factory constructor is not required to create a new instance and may return a cached object or subtype, whereas a generative constructor always allocates a fresh instance of the exact class. Option B is tempting because factories resemble static methods, but they remain part of the constructor namespace and are called with standard constructor syntax.
Read the full bite: What is a Dart factory constructor and common use cases?
Question 7 of 30
You need to parse a dynamic JSON array into List<Product> where some objects may contain missing or mistyped fields. Which approach best preserves type safety and prevents runtime crashes?
Show the answer
Answer: a · Use a factory constructor that checks runtime types and defaults, then map over the list while isolating or skipping invalid rows.
Defensive parsing inside a factory constructor centralizes validation and runtime type checks in the model layer, while isolating or skipping invalid rows during mapping prevents one malformed item from crashing the entire list. Option C spreads uncertainty by pushing null handling into the UI, and C fails because a Map cannot be directly cast to a custom class.
Read the full bite: How do you model Product and safely parse JSON into List<Product>?
Question 8 of 30
In main(), sync prints 1 and 6 surround a Future(2), microtask(3), Future(4), and microtask(5). What is the output order?
Show the answer
Answer: d · 1, 6, 3, 5, 2, 4
The event loop finishes all synchronous code first, then fully drains the microtask queue before processing any event queue callbacks, yielding 1, 6, 3, 5, 2, 4. Option B is tempting because it follows call order, but Dart strictly processes entire microtask batches before touching events.
Read the full bite: Describe the Dart event loop and queue execution order
Question 9 of 30
What happens when you call listen() again on a single-subscription Dart stream after the first listener has finished?
Show the answer
Answer: d · It throws a StateError because only one listener is ever permitted
A single-subscription stream allows exactly one listener over its entire lifetime, so calling listen again throws a StateError even after the first listener completes. The idea that it resumes where the first left off is a common misconception because the runtime enforces the one-listener contract strictly, treating the stream as consumed rather than pausable.
Read the full bite: Explain single-subscription vs broadcast Streams in Dart
Question 10 of 30
When using Future.wait to fetch user profiles concurrently, how do you isolate individual failures while preserving successful results?
Show the answer
Answer: b · Attach catchError to each future before passing the list to Future.wait, then filter out nulls afterward
Attaching catchError to each future guarantees every item resolves, so Future.wait yields a full list where nulls represent failures that can be filtered out. Wrapping Future.wait in try-catch is a common mistake because it catches the batch error but discards all successful profiles that had already completed.
Read the full bite: Fetch user profiles concurrently and handle individual failures
Question 11 of 30
When a Text widget rebuilds with a new string but the same key, what happens to its Element and RenderObject?
Show the answer
Answer: b · The existing Element compares the new Widget and, if compatible, updates the RenderObject without recreating either object.
Because the new Widget matches the existing Element's type and key, the Element is updated in place and calls updateRenderObject on the existing RenderObject, avoiding recreation of both. Distractor A is tempting but wrong because the Element is also reused, not recreated, which preserves state and keeps rebuilds inexpensive.
Read the full bite: Relationship between Widget, Element, and RenderObject trees and their benefits
Question 12 of 30
Why does Scaffold.of(context) return null when called from a build method that returns a Scaffold?
Show the answer
Answer: a · The build method's context belongs to the widget above the Scaffold, so the Scaffold is not an ancestor.
The context in a build method belongs to the widget being built, which sits above the returned Scaffold, so the Scaffold is a descendant and invisible to ancestor lookups like Scaffold.of. Distractor C repeats the common misconception that BuildContext is the widget itself, when it is actually a handle to the Element representing the widget's position in the tree.
Read the full bite: What is BuildContext? Give two tree-interaction examples.
Question 13 of 30
In Flutter, a parent widget rebuilds frequently due to state changes, but its child displays static data. Which approach correctly prevents the child's build method from running unnecessarily?
Show the answer
Answer: c · Extract the child into its own StatelessWidget and invoke it with a const constructor.
Extracting the child into its own StatelessWidget and invoking it with const creates a build boundary, allowing Flutter to reuse the existing element and skip calling build on that subtree. Wrapping it in a RepaintBoundary is a common misconception because that only reduces paint cost during rasterization, not build-phase work.
Read the full bite: How can you prevent unnecessary child rebuilds in Flutter?
Question 14 of 30
In Flutter, two sibling widgets need to read and modify the same piece of data. Where should that state live?
Show the answer
Answer: b · In the closest common ancestor widget, passed down via constructors and callbacks
Placing the state in the closest common ancestor ensures both siblings receive the value and can send events back up via callbacks. Keeping the state inside one child is incorrect because the sibling cannot access it declaratively, violating Flutter's unidirectional data flow.
Read the full bite: Explain lifting state up with a concrete scenario and benefits
Question 15 of 30
In a Row, a childless Container wrapped in Flexible receives 80 pixels flex share but renders at zero width. Why does an Expanded sibling with 120 pixels fill its share?
Show the answer
Answer: b · Flexible applies a loose constraint allowing the child to be smaller than its share, while Expanded applies a tight constraint forcing it to fill.
Flexible passes a loose constraint, letting the child size below its flex share, whereas Expanded passes a tight constraint that forces the child to fill it. Distractor A is wrong because Flexible does participate in proportional space distribution when a flex factor is provided; the Container collapses because loose constraints permit zero width, not because flex is ignored.
Read the full bite: How do Flexible and Expanded differ in a Row or Column?
Question 16 of 30
Which approach correctly truncates a long Text with ellipsis inside a Row while keeping siblings at their intrinsic sizes?
Show the answer
Answer: b · Wrap the Text in Expanded, set overflow to TextOverflow.ellipsis, and maxLines to 1.
Expanded forces the Text to fill only the remaining Row space, giving it a bounded width so ellipsis can truncate properly while siblings keep their natural sizes. A hardcoded Container width avoids the overflow but is brittle across screen sizes and sidesteps Flutter's flex constraint model.
Read the full bite: Fix RenderFlex overflow in Row with long truncating text
Question 17 of 30
What is the best practice for conditionally rendering ListView or GridView based on screen size in Flutter?
Show the answer
Answer: c · Wrap the body in a LayoutBuilder, check if constraints.maxWidth is at least 600, and return GridView.builder or ListView.builder with the same itemBuilder.
LayoutBuilder uses the parent’s BoxConstraints to choose based on actual available width near the 600 dp breakpoint while sharing the same data source. OrientationBuilder is a tempting distractor because a phone in landscape can be wider than a small tablet in portrait, so width—not orientation—is the correct criterion.
Read the full bite: Describe a strategy for ListView on phones and GridView on tablets
Question 18 of 30
When implementing a login form with Flutter's Form widget, which statement accurately describes the validation flow?
Show the answer
Answer: d · Wrap TextFormField widgets in a Form assigned a GlobalKey<FormState>, then call key.currentState.validate() in the submit handler to aggregate all validator results.
Option D is correct because GlobalKey<FormState> exposes the validate() method that aggregates every descendant TextFormField validator and rebuilds error labels, and it is meant to be called on submission. Option C is tempting because it names the right widgets and method, but GlobalKey<Form> is the wrong generic type and does not provide access to validate().
Read the full bite: Build and validate a login form with Form and GlobalKey
Question 19 of 30
When programmatically shifting focus between form fields from a parent StatefulWidget, which practice correctly handles FocusNode lifecycle and prevents focus loss?
Show the answer
Answer: a · Initialize the FocusNode as a state field in initState, attach it to a TextField, and call dispose in State.dispose.
Initializing the FocusNode in initState and disposing it in State.dispose keeps the persistent object stable across rebuilds, preventing dropped focus and ChangeNotifier leaks. Creating it in build is a common error because it recreates the node on every rebuild, instantly destroying keyboard focus.
Read the full bite: What is a FocusNode and why is it useful in forms?
Question 20 of 30
How does Flutter resolve a diagonal drag inside a vertically scrolling list whose items also support horizontal swipes?
Show the answer
Answer: b · The framework compares pointer deltas and the recognizer whose primary axis exceeds touch slop first wins the gesture arena.
Flutter places competing recognizers in a gesture arena and awards victory to the one whose primary axis exceeds touch slop first, causing the losers to reject. The idea that the parent scrollable always wins is a common misconception; hit-testing only invites recognizers into the arena and does not give the scrollable inherent priority.
Read the full bite: What gesture conflict exists in a scrollable list with horizontal swipes?
Question 21 of 30
In Flutter Navigator 2.0, which component receives a typed route configuration and updates the app state to build the Navigator?
Show the answer
Answer: d · RouterDelegate
RouterDelegate receives the typed configuration via setNewRoutePath, updates the app state, and builds the Navigator declaratively. RouteInformationParser only converts raw URL strings into typed configurations and does not manage state or widgets.
Question 22 of 30
Inside a GoRoute builder for path '/users/:userId', what is the correct way to provide the userId to the target widget?
Show the answer
Answer: d · Read state.params['userId'] and handle the possibility of a null value
GoRouter exposes matched path parameters in the state.params map, and because the map values are nullable Strings you must null-check before using the userId. Using ModalRoute.of or parsing context.location manually bypasses GoRouter's declarative state, while state.extra breaks deep linking and web navigation because it is not serialized to the URL.
Read the full bite: Implement a GoRouter path parameter route and access userId in a widget
Question 23 of 30
Why does StatefulShellRoute.indexedStack preserve a tab's pushed pages and scroll position when the user switches away and returns?
Show the answer
Answer: d · Each branch keeps its own Navigator and stays mounted in the IndexedStack
Each StatefulShellBranch owns a Navigator and remains mounted in the IndexedStack, so its state survives switches. A single shared Navigator (last option) would mix the tabs' histories together.
Read the full bite: Per-tab navigation stacks with StatefulShellRoute
Question 24 of 30
A details screen should rebuild only when totalPrice exceeds $100, not when other CartModel fields change. Which implementation is correct?
Show the answer
Answer: a · Use Selector with a selector returning totalPrice > 100 so it rebuilds only when the boolean flips
Selector listens to a derived slice and uses equality checks to rebuild only when the selected boolean changes. Watching the entire CartModel would cause unnecessary rebuilds whenever unrelated fields mutate.
Question 25 of 30
Which strategy best scopes widget rebuilds to a single row when toggling a favorite in a long Flutter list?
Show the answer
Answer: d · Assign a ValueKey with the productId to each row, pass a per-item ValueNotifier to a const widget, and wrap only the favorite icon in a ListenableBuilder.
Option D isolates per-item state by pairing a ValueNotifier with ListenableBuilder inside a const widget with stable keys, so only the icon rebuilds. Option A is tempting because immutable state is usually good practice, but emitting an entire new list causes every row to rebuild instead of just the tapped item.
Read the full bite: How do you manage product list and favorite state without full rebuilds?
Question 26 of 30
What is the key mechanical difference between manually lifting state up and using Provider to share it?
Show the answer
Answer: c · Provider uses InheritedWidget to let descendants access state without intermediate widgets passing it
Provider exposes state through an InheritedWidget, allowing descendants to access or mutate data directly without intermediate widgets accepting and forwarding arguments. The distractors repeat common misconceptions: Provider is scoped rather than global, manual callbacks become unwieldy in deep trees, and Provider localizes rebuilds rather than eliminating them entirely.
Read the full bite: Explain lifting state up in Flutter and how Provider simplifies it
Question 27 of 30
Which strategy best distinguishes a transport timeout from a malformed JSON payload when using Dio?
Show the answer
Answer: c · Check DioExceptionType for connectivity issues, then parse JSON in a separate try-catch after confirming HTTP success
The correct answer reflects the layered approach: inspect DioExceptionType for transport failures first, then isolate JSON deserialization in its own try-catch so schema mismatches are not confused with wire errors. Option B is wrong because a generic catch block makes it impossible to tell whether the failure came from the network or the mapper.
Read the full bite: Describe a robust error handling strategy for network requests
Question 28 of 30
When evaluating manual JSON serialization against json_serializable for a growing Dart codebase, which trade-off is most accurate?
Show the answer
Answer: d · Manual serialization avoids build dependencies but risks boilerplate drift, whereas code generation adds compile-time latency while producing equivalent runtime performance.
Option D captures the core trade-off: manual methods avoid build steps but become error-prone at scale, while json_serializable introduces build_runner latency but generates plain Dart that performs identically at runtime. Option A is a tempting distractor because many candidates mistakenly believe code generation affects end-user performance, but the card emphasizes runtime is identical.
Read the full bite: Compare manual JSON serialization versus json_serializable
Question 29 of 30
Why might you move network request logic out of a widget that currently uses setState and into a state management library?
Show the answer
Answer: a · Because the logic becomes hard to unit test, easy to duplicate, and can leak if the widget disposes mid-request
The card identifies these exact setState limitations: logic trapped in the widget is hard to test, easy to duplicate across screens, and can leak if setState is called after disposal. Distractor B is wrong because setState can be used after a fetch initiated in initState; the issue is maintainability, not a framework ban.
Read the full bite: How would you manage network request state in a Flutter widget?
Question 30 of 30
What is the fundamental reason legacy SharedPreferences operations like getInstance and setters are asynchronous?
Show the answer
Answer: d · Dart runs on a single event-loop isolate for UI work, and platform channel disk I/O must not block that thread.
Dart's single event-loop isolate drives the UI, so blocking platform channel disk I/O would drop frames and create jank. Distractor B is wrong because legacy getters like getInt are synchronous once the in-memory cache is populated by getInstance, which the card identifies as a common misconception.
Read the full bite: Why is Flutter local storage async and how do you use shared_preferences?
Could you explain these out loud?
That is what an interview actually tests. Tezvyn gives you questions like these with what the interviewer is really checking, the answer that lands, and the mistake that ends the conversation, in the four minutes before your next meeting.
The iPhone app is on the way
We are building it. Until it lands, nothing here is held back from you: every interview card, your saved cards, streaks and the job board all work in Safari, plus hundreds of free practice quizzes of thirty questions each. Sign in and it all carries over to the app the day it arrives.
Want it as an icon? Tap Share at the bottom of Safari, then Add to Home Screen. It opens full screen and the cards you have read stay available offline.