Dart Interview Questions
Real Dart interview questions and answers on null safety, Future vs Stream, and mixins.
A curated set of Dart interview questions, ordered roughly from fundamentals to more advanced — the kind you'll actually be asked in real screens and on-sites.
Null safety
Q: What is sound null safety, and what does "sound" mean specifically?
Every type in Dart is non-nullable by default; a variable can only hold null if its type is explicitly marked nullable with ?. "Sound" means the compiler can prove — not merely assume — that a non-nullable variable never holds null at runtime, which both prevents null-reference errors at compile time and lets the compiler skip runtime null checks it has already verified are unnecessary.
Q: What's the difference between late and a nullable (?) type as ways of deferring a variable's initialization?
late keeps the variable non-nullable and promises the compiler it will be assigned before it's ever read — reading it early throws a LateInitializationError. A nullable type (String?) instead makes "not yet set" an explicit, always-valid state (null) that every use site must handle. Use late when you're certain about initialization order and don't want to deal with null-checking later; use nullable when "absent" is a legitimate, ongoing possibility.
Async programming
Q: What's the difference between a Future and a Stream?
A Future<T> resolves to a single value (or error) at some point in time. A Stream<T> delivers a sequence of values over time, arriving whenever each one is ready — the asynchronous equivalent of an iterable versus a single value. Flutter's StreamBuilder widget rebuilds part of the UI each time a Stream emits a new value.
Q: How would you run two independent asynchronous operations concurrently instead of one after another?
Use Future.wait([futureA, futureB]) rather than await futureA; await futureB; — awaiting them sequentially needlessly serializes two operations that don't depend on each other's results, while Future.wait starts both immediately and resolves once all of them complete.
OOP design
Q: What's the difference between using a mixin (with) and using inheritance (extends) to share code between classes?
Inheritance models a true "is-a" relationship, and Dart only allows extending one superclass. A mixin shares reusable behavior across otherwise-unrelated classes, and a class can apply any number of mixins — but a mixin cannot define its own constructor, which is one thing only a real superclass can provide.
Q: What is a factory constructor, and give a practical example of when you'd reach for one.
A factory constructor can return an existing instance rather than always creating a new one, or run logic to decide exactly what to construct — unlike a regular constructor, which always builds a fresh instance of its own class. A common example is User.fromJson(json), which parses a Map and constructs a User from it, or a singleton logger that always returns the same shared instance rather than a new one on every call.
Testing and packages
Q: What's the difference between test() and group() in Dart's test package?
test() defines one individual test case with its own assertions. group() is purely organizational — it nests related tests under a shared description for more readable output, and can itself be nested, but contains no assertions of its own.
Q: Why must a call that's expected to throw be wrapped in a closure when used with expect and throwsArgumentError (or a similar matcher)?
Because the matcher itself needs to invoke the code inside its own error-catching logic in order to catch and check the exception. If the throwing call were passed to expect already evaluated, the exception would be thrown — uncaught — before expect ever ran, crashing the test instead of producing a clean pass or fail.
Q: What's the difference between dependencies and dev_dependencies in pubspec.yaml?
dependencies are required at runtime and ship inside the compiled app or package. dev_dependencies — testing libraries, linters, code generators — are only needed during development and are never bundled into a release build.
Q: What does the caret (^) version constraint mean for a Dart/Flutter package dependency?
^1.2.3 means any version from 1.2.3 up to, but not including, 2.0.0 — accepting routine minor and patch updates automatically while refusing a major version bump, which is where semantic versioning allows breaking changes to occur.