Bryn Flow

🎯 Dart Interview Questions & Answers

The questions that come up again and again in Dart language interviews, with concise, correct answers — grouped by difficulty.

← Dart Track

Beginner Fundamentals

What is the difference between var, final, and const?

var declares a mutable variable with an inferred type. final can be assigned only once, but its value can be computed at runtime (e.g. from a function call). const must be a compile-time constant — known and fixed before the program runs — and const objects are deeply immutable and canonicalized (identical const values share the same instance).

What does sound null safety mean?

It means the compiler can statically guarantee that a variable of a non-nullable type (like String) can never actually be null at runtime — not just as a convention, but enforced throughout the type system, including in generics and across library boundaries. Only types explicitly marked with ? (like String?) may hold null.

What is the difference between == and identical() in Dart?

== checks value equality, using a class's overridden == operator if it defines one (or reference equality by default if it doesn't). identical() always checks reference equality — whether two variables point to the exact same object instance in memory — regardless of any overridden ==.

What is the purpose of the required keyword on a named parameter?

By default, named parameters (in curly braces) are optional. Marking one required makes the compiler enforce that callers must supply it, while keeping the readability benefit of a named argument at the call site — a middle ground between purely positional and purely optional parameters.

What is string interpolation, and how does it work in Dart?

String interpolation embeds expressions directly inside a string literal using $variable for a simple identifier or ${expression} for anything more complex (method calls, arithmetic). Dart evaluates the expression and calls its toString() to produce the final string — e.g. 'Total: ${price * qty}'.

Intermediate Types & Async

What is the difference between a mixin and abstract inheritance?

Dart classes support only single inheritance (extends), so a class can have exactly one superclass. A mixin lets you compose reusable behavior into a class via with, without establishing a strict "is-a" superclass relationship and without the mixin having its own constructor — useful when the same capability (like logging or comparability) needs to be shared across unrelated class hierarchies.

What is a Future, and how does async/await relate to it?

A Future<T> represents a value of type T that will be available at some point, possibly after failing instead. async/await is syntactic sugar over working with Futures directly (.then()/.catchError()) — marking a function async makes it return a Future automatically, and await pauses execution within that function until the awaited Future resolves, without blocking the underlying event loop for other work.

What is the difference between a broadcast Stream and a single-subscription Stream?

A single-subscription stream (the default) allows only one listener over its lifetime and typically starts producing events only once listened to — suited for a one-time sequence like a file read. A broadcast stream (StreamController.broadcast()) allows multiple simultaneous listeners, but doesn't buffer events for listeners that subscribe late — they only see events emitted after they start listening.

How does Dart's fold differ from reduce on a collection?

Both combine a collection into a single value, but fold takes an explicit initial value and works correctly on an empty collection (returning the initial value). reduce uses the first element as the initial accumulator and throws a StateError if called on an empty collection, since there's no first element to start from.

What does the extends, implements, and with keyword each do when defining a class?

extends inherits implementation and interface from exactly one superclass, allowing method overriding via super. implements adopts a class's interface (its member signatures) without inheriting any implementation — you must implement every member yourself; Dart has no separate interface keyword, any class can serve as an interface. with mixes in a mixin's implementation into a class, composable across multiple mixins.

Advanced Deep Dives

What is an isolate, and how does it differ from a thread?

An isolate is Dart's unit of concurrency: an independent worker with its own memory heap and event loop, unable to directly share mutable state with other isolates — communication happens exclusively via message passing (send/receive ports), which are copied (or, for some types, transferred) across the boundary. This is fundamentally different from OS threads sharing one memory space, and it's why Dart avoids traditional shared-memory race conditions and doesn't need locks/mutexes for isolate-to-isolate communication — the tradeoff is message-passing overhead for anything beyond small primitive/copyable data.

How does Dart's event loop schedule microtasks versus the event queue, and why does it matter for async ordering?

Dart runs a single-threaded event loop with two queues: the microtask queue (used internally by Future callbacks, like the code after an await) and the event queue (I/O, timers, UI events). The event loop always fully drains the microtask queue before processing the next event queue item. This means a chain of resolved Futures can starve timer/IO callbacks if it keeps scheduling more microtasks — a subtle source of bugs where a Timer or stream event seems "delayed" behind a long chain of synchronously-resolving awaits.

What is the difference between a Future and a Completer, and when would you use a Completer directly?

A Future is a read-only handle to an eventual value. A Completer<T> is the mechanism used to create and control a Future manually — you expose completer.future to callers while retaining the ability to call complete() or completeError() yourself. It's needed when a Future's completion is triggered by something that isn't naturally another Future or async function — e.g. bridging a callback-based API, or (as in a task queue) deferring completion until a scheduler decides to run the work.

How does Dart achieve fast startup with JIT during development but fast runtime performance with AOT in release?

During development, the Dart VM uses JIT (just-in-time) compilation: source is compiled to native code on the fly, which enables features like hot reload (patching a running VM) at the cost of some warm-up and larger runtime footprint. For release, dart compile (or Flutter's release build) uses AOT (ahead-of-time) compilation to produce a fully native binary or snapshot with no VM/interpreter step at startup, trading hot-reload capability for smaller size, faster startup, and predictable native-level performance.

What is the difference between a covariant and a regular generic type parameter, and why does it matter for method overriding?

Dart generics are invariant by default — a List<Dog> is not treated as a List<Animal> for parameter purposes, since that would be unsound if you tried to add a Cat to it through the Animal-typed reference. The covariant keyword lets you explicitly relax this in a specific override — e.g. a subclass method that legitimately narrows a parameter type — shifting the type check from compile time to runtime for that parameter, which is occasionally needed but should be used sparingly since it trades static safety for flexibility.

Keep practicing

Take the Dart practice quiz or walk through a full project tutorial.

Practice Quiz