Flutter · 15 min
Flutter and Dart interview questions and answers
Flutter interview questions and answers on widgets, state, navigation and testing, plus Dart questions on async, the event loop and null safety.
Flutter · 15 min read
Twelve senior Flutter interview questions on rendering, keys, state, isolates, platform channels and architecture, with the follow-up to each.
This page is for Flutter engineers interviewing at senior level, or at mid level for a team that asks senior questions. You get twelve senior Flutter interview questions, the ones I would ask a candidate at that level, what each one scores, the weak answer, the answer that holds, and the follow-up that decides it. I have interviewed Flutter, React Native and native mobile candidates from the hiring side, and the Flutter loops separate on the same thing as the others: whether you can say what the package you name costs.
A mid-level Flutter answer defines the thing. A senior answer names requirements, alternatives, how it is measured, how it fails, and how you would migrate away from it. The shape I listen for:
| Step | What it sounds like |
|---|---|
| Clarify | “Which platforms, and what failure is acceptable?” |
| Answer | The recommendation in one or two sentences |
| Mechanics | Ownership, lifecycle, threading, or where it sits in the rendering pipeline |
| Trade-off | One real alternative and the condition that would change the choice |
| Verify | The trace, test, metric or rollout that proves it |
An answer that stops after the second row is a mid-level answer, however many years the candidate has.
What is scored: whether you know which one is cheap.
Weak answer: “Widgets are the UI, and Flutter redraws them when state changes.”
Senior answer: “A widget is an immutable, cheap description, rebuilt often. An element is the widget’s place in the tree. It is long-lived and holds the state of a StatefulWidget, and Flutter reuses it when the new widget has the same type and key. A render object does layout, painting and hit testing. It is the heaviest of the three and is updated, not recreated. So a rebuild is not a repaint, and rebuilding a widget is usually not the expensive part.”
The follow-up: “A list of stateful items is reordered and the state stays in the wrong row. Why?” Without keys, Flutter matches elements by type and position, so each element keeps its state and is given a different widget. A ValueKey built from the item’s id moves the state with the item. This is where senior Flutter answers separate: the element tree and keys, not the widget catalogue.
Senior answer: “In layout, a parent passes constraints down, a minimum and maximum width and height. The child picks a size within them and passes it up. The parent then decides the child’s position. A child cannot choose a size outside its constraints, which explains most ‘why is my widget not the size I set’ questions.”
The follow-up: “Why does changing one child’s size sometimes re-lay out half the screen?” Because layout propagates up until it hits a relayout boundary. A render object becomes one when its constraints are tight, when it sizes itself, or when the parent does not use its size.
What is scored: evidence, and knowing what changed in the engine.
Senior answer: “Profile mode, on a real device of the affected class, never a debug build. In the DevTools performance view I first decide which thread misses the budget: the UI thread, which is Dart build and layout work, or the raster thread, which is painting and GPU work. They have different fixes. Then I reduce it to one repeatable interaction, change one cause, and compare against the baseline. Over several runs, not the single best one. Then I add a benchmark or a production metric, so the same regression cannot come back unnoticed.”
The trap: “Impeller fixed jank.” Impeller removed most of the old shader warm-up problem on the platforms where it is the renderer. Layout work, excessive painting, oversized images and a blocked UI isolate still miss frames.
Opacity expensive, and when is RepaintBoundary worth adding?”Senior answer: “An Opacity with a value between 0 and 1 can paint its child into an offscreen layer and composite it, and when it animates that happens every frame. For a flat colour, alpha on the colour avoids the layer, and for an animated fade FadeTransition or AnimatedOpacity is the better tool. A RepaintBoundary gives a subtree its own layer, so a frequently repainting child does not repaint its neighbours. It costs memory, so I add one where the trace shows repaint spreading, not around every widget.”
The follow-up: “Would you wrap every list item in a RepaintBoundary?” Not by default. That is premature optimisation: more layers, more memory, no measured benefit.
Senior answer: “Decoded image size, not file size. A 12 megapixel photo shown as a thumbnail is decoded at full size unless I pass cacheWidth or cacheHeight. Flutter’s ImageCache holds decoded images in memory, and a disk cache package reduces network work but does nothing for the decoded size. Then I check what is retained: controllers, streams, timers and listeners that were never disposed, and closures that captured a BuildContext or a whole State.”
The follow-up: “The Dart heap looks fine, and the process is still large.” Then the memory is somewhere else: graphics resources such as decoded images, or native memory held by a plugin. DevTools shows the Dart side, so compare it with the platform’s own memory tools before deciding where the leak is. A cache with no size limit and no eviction policy is a leak with a better name.
What is scored: why you would stop using it.
Weak answer: “Riverpod, because it is the best one.”
Senior answer: “It depends on what owns the state and for how long. Local UI state stays in the widget with setState or a ValueNotifier. Shared, asynchronous workflows benefit from something that makes dependencies, cancellation and transitions explicit, which in one codebase can mean BLoC for the complex flows and plain notifiers everywhere else. I would compare on who owns the state, how transitions are represented, what rebuilds, how it is tested and what migrating away would cost. And I would stop using it if the team could no longer explain where a given piece of state lives.”
The follow-up: “Two senior engineers disagree: BLoC or Riverpod.” Move the argument from opinion to one hard use case, prototype it both ways, compare on testability, boilerplate, team familiarity and performance, decide, and write the decision down.
Senior answer: “Not in the BLoC. The BLoC has no BuildContext and should not. It emits a success state. A BlocListener in the UI reacts to that state once and calls the navigator and the ScaffoldMessenger. A BlocBuilder would rebuild for it, which is the wrong tool for a one-time effect.”
The follow-up: “Why not navigate from inside the BLoC with a global navigator key?” Because the business logic now depends on one screen’s navigation, the BLoC cannot be tested without a navigator, and the same BLoC breaks when another screen reuses it. The BLoC reports what happened. The UI decides what that means on screen.
Senior answer: “compute and Isolate.run are for one bounded job: one input, one result, such as parsing a large JSON payload. Isolate.spawn gives me a long-lived isolate with ports, for repeated work or a worker protocol. Isolates do not share memory, so messages are copied, and starting one has a cost. For large binary data, TransferableTypedData moves the buffer instead of copying it. And on the web, compute runs on the same event loop, because browsers do not have isolates in the same way.”
The trap: moving a ten-item sort into an isolate. Spawning costs more than the work.
Senior answer: “Yes, with limits. The root isolate’s token is passed to the background isolate, which initialises BackgroundIsolateBinaryMessenger with it before calling a compatible plugin. It can send platform messages and receive their responses, but not unsolicited messages from the host, and UI work stays on the root isolate.”
Senior answer: “A MethodChannel for request and response calls, an EventChannel for a stream from native, like sensor updates. Pigeon generates typed code for both sides, so there are no string method names or untyped maps to get wrong. FFI when there is a C interface and the call overhead matters. Every channel call serialises data and crosses a boundary, so I batch, avoid chatty APIs, and keep blocking native work off the platform thread.”
The follow-up: “A native SDK sends location updates twenty times a second, and the UI janks.” Throttle or aggregate on the native side, send only what the screen needs, and measure payload size and latency before and after.
Senior answer: “As a production rollout. Every schema step is versioned, every supported source version has a path to the current one, and each step is transactional or resumable. I test against anonymised production-sized databases, and on a device that is low on storage or gets interrupted mid-migration. Then staged rollout with migration telemetry, and a recovery plan decided before shipping. Data that can be rebuilt, like a cache, can be dropped; user data cannot.”
What is scored: order of operations.
Senior answer: “First the lifecycle leaks, controllers and subscriptions that are never disposed, because they are bugs today. Then characterisation tests on what the screen does now. Then one seam at a time: API calls into a repository, logic into a BLoC or notifier, the build method split into small stateless widgets, each step shippable. I keep UI state separate from domain data, and I leave local what does not need to be shared.”
The trap: “Rewrite it with Clean Architecture.” A rewrite with no tests and unknown behaviour is the riskiest option on the table.
Prompt: “The app freezes for about a second when the user opens a screen with a complex list.”
Strong answer: “I would record the transition in profile mode and look at the UI thread. A freeze on entry usually means synchronous work during build or initState: decoding a large JSON payload, building every row eagerly instead of lazily, or reading a file. If it is parsing, it moves to Isolate.run. If it is the list, I check it uses ListView.builder so only visible rows are built. Then I measure the same transition again.”
Push: “The trace shows the UI thread is fine. The raster thread is not.” Now it is painting: an expensive clip, a saveLayer from opacity or a shadow on every row, or images decoded far larger than displayed.
Push: “It only happens the first time.” Something is being prepared once: a font, an image, a database opening on the main isolate. Find it in the trace, then decide whether to move it earlier, off the main isolate, or later.
What I listen for in that exchange: the candidate splits the UI thread from the raster thread before proposing a fix, and measures again after it.
| Follow-up type | Flutter example | What a strong answer does |
|---|---|---|
| Change the constraint | “Now it must also run on the web.” | Notes what changes: isolates, plugins, renderer |
| Break it | “The platform channel call times out.” | Names the error mapping and the fallback |
| Scale it | “Eight teams share this package.” | Moves from code to ownership and versioning |
| Measure it | “How do you know it is faster?” | Names profile mode, the device and the metric |
| Cost it | “What does code generation cost you here?” | Build time, review noise, migration |
Answer each question aloud, then answer the follow-up without reading it. Where your answer names a package but not what it costs, that is the card to rework. The broad question set, with Dart, is on Flutter interview questions and answers, and the architecture round is on Flutter architecture interview questions.

Flutter · 15 min
Flutter interview questions and answers on widgets, state, navigation and testing, plus Dart questions on async, the event loop and null safety.

Flutter · 10 min
Flutter architecture interview questions on layering, state ownership, packages and testing, with the decision each one tests and what it costs a team.

Flutter · 12 min
What a Flutter machine coding interview asks, how the build is graded, and the state and structure choices that decide it before the UI is finished.