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 · 12 min read
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.
This page is for Flutter engineers with a Flutter machine coding interview or coding round ahead, live or take-home. You get the difference between the two kinds of coding round, the practical exercises worth rehearsing, how to spend the first fifteen minutes, what the interviewer grades, a worked paginated feed in Dart with the test that proves it, and the Flutter traps that cost points at the end. The grade in a machine coding round is usually decided by the structure you choose early, not by how the screen looks when time runs out.
How to talk while you type is covered in Mobile live coding interview. This page is about what you build and how it is judged.
A machine coding round asks you to build a small working feature: a screen or two, a data source, some state, often with a mock API. An algorithm round asks you to solve a data structure problem in Dart, usually without Flutter at all. Ask the recruiter which one it is, which environment you will use, whether packages are allowed, and how long you have. The answers change what you open before the call.
500 Flutter Interview Questions has three practical rounds in its end matter, each with the minimum evidence or the rubric an interviewer grades against. In short:
CustomPainter, an explicit animation, semantics, reduced-motion behaviour and tests.Build the first one against a timer before any machine coding interview. It covers most of what the other tasks test: state that changes under you, async work that can arrive late, and a UI that has to survive both.
The first minutes decide how the rest goes. Spend them on:
Pick the lightest tool that makes the state explicit. A ChangeNotifier or ValueNotifier with a clear owner is enough for a feed and is fast to write. If the company’s codebase uses BLoC or Riverpod and the interviewer asks for it, use it. Otherwise, reaching for the heaviest package under a clock is read as a judgement miss: you spend twenty minutes on setup that proves nothing about the problem.
Say why you chose it: “I will use a ChangeNotifier because the state is small and has one owner. In a larger app with shared state, I would put this behind the team’s state management package.” That sentence is worth more than the package.
The book’s evidence list for the feed task is the core of the scorecard. This is how I read it in the room:
| What I look for | Strong | Weak |
|---|---|---|
| Logic outside widgets | The controller works and is tested without a widget | Fetching and state inside build or initState |
| Races handled | A stale response cannot overwrite newer data | Refresh and pagination can interleave and corrupt the list |
| Failure keeps data | A failed page shows an error and keeps what was loaded | One error clears the screen |
| One test that matters | A unit test on the race or the failure case | No tests, or only a widget smoke test |
| Disposal | Controllers and subscriptions disposed | Leaks that only show up later |
For the refactoring task the order matters more than the result: finding the lifecycle leak before redesigning everything, adding boundaries only where they buy testability, and keeping local what does not need to be shared.
The animation task is graded on details most candidates skip. shouldRepaint returns true only when a painted value changed. The AnimationController is disposed, and the State that owns it provides the ticker. Constraints and device pixels are handled, so the ring stays sharp at any size. Semantics give a screen reader a label and a value, and you can say how much difference your golden tests tolerate before they fail.
The plan, in three sentences: “A FeedController owns the items, the loading and error flags, and the next cursor. Every refresh increases a generation number, and any response from an older generation is dropped. A failure sets the error and keeps the items.”
class FeedController extends ChangeNotifier {
FeedController(this._api);
final FeedApi _api;
List<Post> items = const [];
bool loading = false;
Object? error;
String? _cursor;
bool _hasMore = true;
int _generation = 0; // a refresh starts a new generation
Future<void> refresh() => _load(reset: true);
Future<void> loadMore() => (loading || !_hasMore) ? Future.value() : _load(reset: false);
Future<void> _load({required bool reset}) async {
final gen = reset ? ++_generation : _generation;
loading = true;
error = null;
notifyListeners();
try {
final page = await _api.fetch(cursor: reset ? null : _cursor);
if (gen != _generation) return; // a newer refresh owns the list now
items = reset ? page.items : [...items, ...page.items];
_cursor = page.next;
_hasMore = page.next != null;
} catch (e) {
if (gen != _generation) return;
error = e; // keep what was already loaded
} finally {
if (gen == _generation) {
loading = false;
notifyListeners();
}
}
}
}The test that proves the race is handled, with a fake API that lets the test decide when each request completes:
class FakeFeedApi implements FeedApi {
final calls = <Completer<FeedPage>>[];
@override
Future<FeedPage> fetch({String? cursor}) {
final call = Completer<FeedPage>();
calls.add(call);
return call.future;
}
}
test('a refresh during loadMore drops the stale page', () async {
final api = FakeFeedApi();
final feed = FeedController(api);
final first = feed.refresh();
api.calls[0].complete(FeedPage([Post('a')], next: 'p2'));
await first;
final more = feed.loadMore(); // page 2 in flight
final again = feed.refresh(); // the user pulls to refresh meanwhile
api.calls[2].complete(FeedPage([Post('b')], next: 'p2'));
await again;
api.calls[1].complete(FeedPage([Post('stale')], next: null));
await more;
expect(feed.items.map((p) => p.id), ['b']);
});What the interviewer asks next, and what a strong answer says:
Future cannot be cancelled. Some HTTP clients offer a cancel token, and I would use it too, to save the network. The generation check is what guarantees correctness, whatever the client does.build.setState or notifyListeners after dispose. An async call returns after the screen is gone. Check mounted in a State, or guard the controller.BuildContext across an async gap. Navigating after an await without checking mounted works in the demo and crashes in review.build method with the fetch, the parsing and the UI cannot be tested, and the interviewer will ask how you would test it.pumpAndSettle with a spinner on screen. An indefinite animation never settles, and the widget test times out. Use pump with a duration.None of these shows up in the first ten minutes. They show up in review, when there is no time left to fix them, so check your build against this list before you say it is done.
Whichever you pick, answer out loud and time yourself. Saying the answer is the part a page cannot do for you, and it is the part the round scores.

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 · 15 min
Twelve senior Flutter interview questions on rendering, keys, state, isolates, platform channels and architecture, with the follow-up to each.

Live coding · 14 min
How a mobile live coding round is scored, a 45-minute plan, what to say at each stage, a worked Swift example, and how to recover when you stall.