Top 60 Dart Interview Questions (2026)

The 60 Dart questions interviewers actually ask, with direct answers, runnable code, and what the interviewer is listening for. Grouped for freshers, intermediate, and experienced rounds.

60 questions with answers

What Is Dart?

Key Takeaways

  • Dart is a client-optimized, statically typed language from Google, built for fast apps on mobile, web, desktop, and server.
  • It powers Flutter, so most Dart interviews sit inside a Flutter hiring loop and test both the language and how you'd use it in an app.
  • Interviews probe sound null safety, async (Future, Stream, isolates), and the object model (mixins, named constructors), not just syntax.
  • This page is a question bank: work your tier first, read one tier up, and say answers out loud because delivery gets evaluated too.

Dart is a client-optimized, statically typed programming language created by Google and first released in 2011. It compiles to native machine code for mobile and desktop (via AOT) and to JavaScript or WebAssembly for the web, which is how one Dart codebase ships across platforms. It's best known as the language behind Flutter, so most Dart interviews happen inside a Flutter hiring loop and test both the language itself and how you apply it in a real app. According to Google's official Dart documentation, everything you can place in a variable is an object, type annotations are optional because Dart infers types, and privacy is a naming convention (a leading underscore) rather than a keyword. In interviews, Dart questions probe sound null safety, asynchronous programming (Future, Stream, isolates), and the object model (mixins, named and factory constructors), not memorized trivia. This page collects the 60 questions that come up most, each with a direct answer and runnable code. Increasingly the first Dart round runs as a live AI coding interview, so pair this bank with our AI interview preparation guides for the format side.

60Questions with answers on this page
3Difficulty tiers: freshers, intermediate, experienced
24Runnable code snippets you can practice from
45-60 minTypical length of a Dart technical round

Watch: Dart Programming Tutorial - Full Course

Video: Dart Programming Tutorial - Full Course (freeCodeCamp.org, YouTube)

Test yourself and earn a certificate

10 quick questions. Score 70%+ to download your Dart certificate.

Jump to quiz

All Questions on This Page

60 questions
Dart Interview Questions for Freshers
  1. 1. What is Dart and what makes it popular?
  2. 2. What is the difference between var, final, and const?
  3. 3. What is sound null safety in Dart?
  4. 4. What do the ?, ??, ?., and ! operators do?
  5. 5. What are the core built-in types in Dart?
  6. 6. What is the difference between a List, a Set, and a Map?
  7. 7. How do you declare functions, and what is arrow syntax?
  8. 8. What are named, optional, and required parameters?
  9. 9. How does string interpolation work in Dart?
  10. 10. How do you define a class and a constructor in Dart?
  11. 11. What are named constructors?
  12. 12. How does inheritance work in Dart?
  13. 13. What are getters and setters in Dart?
  14. 14. What is the cascade operator (..)?
  15. 15. What are collection-if and collection-for?
  16. 16. What does the spread operator (...) do?
  17. 17. What is a Future and how do async and await work?
  18. 18. What is a typedef in Dart?
  19. 19. What is the main function in Dart?
  20. 20. What does the ??= operator do?
  21. 21. What are enums in Dart, and what are enhanced enums?
Dart Intermediate Interview Questions
  1. 22. What is the difference between a Future and a Stream?
  2. 23. What is a mixin and how does it differ from inheritance?
  3. 24. What is the difference between abstract classes and interfaces in Dart?
  4. 25. What is a factory constructor and when do you use one?
  5. 26. What is a const constructor and why does it matter?
  6. 27. How does error handling work in Dart?
  7. 28. How do generics work in Dart?
  8. 29. How do you handle errors from a Future?
  9. 30. Which Iterable methods should every Dart developer know?
  10. 31. How do you implement value equality in Dart?
  11. 32. What are records in Dart 3?
  12. 33. What is pattern matching in Dart 3?
  13. 34. What are sealed classes and why are they useful?
  14. 35. What are extension methods?
  15. 36. Why is a const collection different from a final collection?
  16. 37. What do the is, as, and as? operators do?
  17. 38. What does the late keyword do and when is it appropriate?
  18. 39. What do the class modifiers final, base, and interface do in Dart 3?
  19. 40. How do you serialize and deserialize JSON in Dart?
Dart Interview Questions for Experienced Developers
  1. 41. What are isolates and how do they differ from threads?
  2. 42. How does Dart's event loop and single-threaded concurrency work?
  3. 43. When do you use async/await versus an isolate?
  4. 44. What is the difference between JIT and AOT compilation in Dart?
  5. 45. How does garbage collection work in Dart?
  6. 46. What is a StreamController, and what is the difference between single-subscription and broadcast streams?
  7. 47. How does variance work with Dart generics?
  8. 48. What is dart:ffi and when would you use it?
  9. 49. What are zones in Dart?
  10. 50. What is const canonicalization and why does it matter for performance?
  11. 51. How can a Dart application leak memory, and how would you diagnose it?
  12. 52. How would you migrate a large pre-null-safety Dart codebase to sound null safety?
  13. 53. What does the on clause in a mixin declaration do?
  14. 54. What is a Completer and when do you use one?
  15. 55. How do you write and structure tests in Dart?
  16. 56. How does operator overloading work in Dart?
  17. 57. What are async generators (async*) and sync generators (sync*)?
  18. 58. Design question: how would you build an in-memory cache with expiry in Dart?
  19. 59. How do you run multiple Futures concurrently and handle their results?
  20. 60. What is metadata (annotations) in Dart and how is it used?

Dart Interview Questions for Freshers

Freshers21 questions

The fundamentals every entry-level round checks. If any answer here surprises you, that's your study list.

Q1. What is Dart and what makes it popular?

Dart is a client-optimized, statically typed programming language from Google, first released in 2011. It compiles to native machine code for mobile and desktop through ahead-of-time compilation, and to JavaScript or WebAssembly for the web, so one codebase runs across every platform.

Its popularity comes almost entirely from Flutter: teams write UI once and ship to iOS, Android, web, and desktop. Stateful hot reload during development and ahead-of-time compilation for release give a fast feedback loop while you build and quick startup for users.

Key point: A one-line definition plus the Flutter connection beats a memorized feature list. Interviewers open with this to hear how you organize an answer.

Watch a deeper explanation

Video: Dart in 100 Seconds (Fireship, YouTube)

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

var declares a variable whose type is inferred and whose value can change. final holds a value set exactly once, at runtime, and can't be reassigned. const is a compile-time constant: its value must be known when the code compiles, and it's deeply immutable.

The quick rule: use final for values fixed after one assignment, const for values baked in at compile time, and var only when you genuinely need to reassign.

dart
var count = 3;
count = 4;              // ok, var can change

final name = "Asha";
// name = "Ben";        // error: final is set once

const pi = 3.14159;     // known at compile time
final now = DateTime.now();   // runtime value: final works, const would not

Key point: The follow-up is 'why can't now be const?'. Because const needs a compile-time value and DateTime.now() runs at runtime.

Q3. What is sound null safety in Dart?

Sound null safety means types are non-nullable by default: a String can't hold null, and to allow null you write String?. The compiler tracks nullability and rejects code that might dereference null, so a whole class of runtime null errors becomes a compile error instead.

The word sound matters: if the type says non-nullable, the runtime guarantees it, no escape hatches from mixed legacy code. This is the single most asked Dart topic in interviews.

dart
String name = "Asha";
// name = null;         // compile error, String is non-nullable

String? nickname;       // allowed to be null
nickname = null;        // fine
print(nickname?.length);  // null-aware access, prints null safely

Key point: Say the word 'sound' and explain it. Candidates who only know ? without the guarantee sound shallower.

Q4. What do the ?, ??, ?., and ! operators do?

? in a type (String?) makes it nullable. ?? gives a default: a ?? b returns a if non-null, else b. ?. calls a member only if the target is non-null, otherwise the whole expression is null. ! asserts a nullable value is actually non-null and throws if it's wrong.

  • String? The marks the type nullable.
  • The ?? 'Guest' supplies a fallback when name is null.
  • name?.length reads length only when name isn't null.
  • name! promises name isn't null; it throws if it is, so use it sparingly.
dart
String? name;
print(name ?? "Guest");   // Guest
print(name?.length);      // null (no crash)

name = "Asha";
print(name!.length);      // 4, ! asserts non-null

Key point: Volunteering that ! throws is what separates understanding from pattern-matching syntax. Interviewers watch for casual ! overuse.

Q5. What are the core built-in types in Dart?

The everyday types are int and double (both num), String, bool, and the collections List, Set, and Map. There's also the special Object (base of everything except Null), dynamic (opts out of static checks), and Null.

One detail interviewers like: int and double are both subtypes of num, so you can type a variable as num when it might be either. And there's no separate char type; a single character is just a one-length String.

dart
int age = 30;
double price = 9.99;
num either = 5;        // holds int or double
String city = "Tokyo";
bool active = true;
List<int> scores = [91, 84];
Map<String, int> ages = {"Asha": 31};

Q6. What is the difference between a List, a Set, and a Map?

A List is an ordered collection that allows duplicates and indexes by position. A Set is an unordered collection of unique values with fast membership tests. A Map stores key-value pairs with unique keys and fast lookup by key.

Pick by need: order and duplicates mean List, uniqueness means Set, lookup by a key means Map.

dart
List<int> nums = [1, 2, 2, 3];
Set<int> unique = {1, 2, 3};
Map<String, int> ages = {"Asha": 31, "Ben": 26};
print(unique.contains(2));   // true
print(ages["Asha"]);         // 31
ListSetMap
OrderedYesNo (by default)Insertion order
DuplicatesAllowedNot allowedUnique keys
Access byIndexMembership testKey
Literal[1, 2, 3]{1, 2, 3}{'a': 1}

Key point: The empty-literal trap: {} is an empty Map, not a Set. To make an empty Set you write <int>{}.

Q7. How do you declare functions, and what is arrow syntax?

A Dart function declares a return type, a name, and parameters, then a body in braces. When the body is a single expression, arrow syntax (=> expr) replaces { return expr; } with a shorter form.

Functions are first-class in Dart: you can store them in variables, pass them as arguments, and return them, which is what makes callbacks and higher-order functions natural.

dart
int square(int n) {
  return n * n;
}

int cube(int n) => n * n * n;   // arrow syntax

void apply(int x, int Function(int) fn) => print(fn(x));
apply(3, square);   // 9

Q8. What are named, optional, and required parameters?

Positional parameters come in order. Optional positional parameters go in square brackets and can be omitted. Named parameters go in curly braces and are passed by name, which reads clearly at the call site. A named parameter marked required must be supplied.

Named parameters are everywhere in Flutter widget constructors, so the question expects fluency here.

dart
String greet(String name, {String greeting = "Hi", required int age}) {
  return "$greeting $name, age $age";
}

greet("Asha", age: 31);                  // "Hi Asha, age 31"
greet("Ben", greeting: "Hello", age: 26);

Key point: Knowing that required is a keyword (not the old @required annotation) signals you're on Dart 2.12 or newer.

Q9. How does string interpolation work in Dart?

You embed an expression in a string with $name for a simple variable, or ${expression} for anything more complex like a method call or arithmetic. It's the readable way to build strings in Dart, and it beats gluing pieces together with the + operator, which reads worse and is easy to get wrong.

Dart also has adjacent string literals (two quoted strings next to each other join automatically) and triple-quoted strings for multi-line text.

dart
var name = "Asha";
var items = [1, 2, 3];
print("Hi $name");                 // Hi Asha
print("Count: ${items.length}");   // Count: 3
print("Upper: ${name.toUpperCase()}");

Q10. How do you define a class and a constructor in Dart?

A class groups fields and methods. The constructor initializes fields, and Dart's shorthand this.field syntax assigns a parameter straight to a field without a body. Every class has a default no-argument constructor unless you define your own.

dart
class Point {
  final int x;
  final int y;

  Point(this.x, this.y);          // shorthand assigns fields

  int get sum => x + y;
}

var p = Point(3, 4);
print(p.sum);   // 7

Q11. What are named constructors?

A named constructor gives a class more than one way to build an instance, using ClassName.name syntax. It's how Dart works around the lack of constructor overloading, and it documents intent at the call site.

You'll see this constantly in Flutter and core libraries, like Point.origin() or DateTime.utc(...).

dart
class Point {
  final int x, y;
  Point(this.x, this.y);

  Point.origin()          // named constructor
      : x = 0, y = 0;
}

var o = Point.origin();   // (0, 0)

Q12. How does inheritance work in Dart?

A class extends exactly one superclass, inheriting its fields and methods. You call the parent's constructor with super, and override a method by writing it again with the @override annotation. Dart has single inheritance, and mixins fill the gap when you need to share code across unrelated classes.

dart
class Animal {
  void speak() => print("...");
}

class Dog extends Animal {
  @override
  void speak() => print("Woof");
}

Dog().speak();   // Woof

Q13. What are getters and setters in Dart?

A getter computes a value that callers read like a field (obj.area), and a setter intercepts assignment (obj.width = 5). You define them with get and set, so a class can expose a clean property interface while hiding logic or validation behind it.

The point is evolution: you can a plain field and later swap in a getter or setter without changing a single caller comes first.

dart
class Rect {
  double width, height;
  Rect(this.width, this.height);

  double get area => width * height;

  set side(double s) {
    width = s;
    height = s;
  }
}

var r = Rect(2, 3);
print(r.area);   // 6
r.side = 4;      // now a square

Q14. What is the cascade operator (..)?

The cascade lets you run several operations on the same object without repeating the variable. obj..a()..b()..c = 1 calls a and b and sets c, then returns the object itself, which is handy when you're configuring one thing.

The catch worth mentioning: a cascade returns the original object, not the result of the last call, so it's for side effects, not for chaining transformations.

dart
var buffer = StringBuffer()
  ..write("Hello")
  ..write(", ")
  ..write("world");

print(buffer.toString());   // Hello, world

Key point: Being able to The a cascade returns (the object, not the last call) is the common follow-up.

Q15. What are collection-if and collection-for?

Inside a list, set, or map literal, Dart lets you use if and for directly to build the collection conditionally or from another iterable. It replaces awkward temporary lists and reads cleanly, especially in Flutter widget trees.

dart
bool loggedIn = true;
var tabs = [
  "Home",
  "Search",
  if (loggedIn) "Profile",        // conditional element
  for (var i in [1, 2, 3]) "Item $i",
];
// [Home, Search, Profile, Item 1, Item 2, Item 3]

Q16. What does the spread operator (...) do?

The spread operator inserts all the elements of one collection directly into another literal, so you build a combined list, set, or map in one expression. Writing ...list unpacks a list inline, and the null-aware form ...?list does the same but safely skips the whole thing when the source is null.

dart
var base = [1, 2, 3];
List<int>? extra;
var all = [0, ...base, ...?extra, 4];
print(all);   // [0, 1, 2, 3, 4]

Q17. What is a Future and how do async and await work?

A Future represents a value that isn't ready yet, like the result of a network call. An async function returns a Future, and await pauses that function until the Future completes, then hands back the value. The event loop keeps running other work during the wait, so nothing blocks.

The mental model: async/await is sequential-looking code that's actually non-blocking underneath.

dart
Future<String> fetchName() async {
  await Future.delayed(Duration(seconds: 1));
  return "Asha";
}

void main() async {
  print("start");
  var name = await fetchName();
  print("got $name");   // runs after 1s, without blocking
}

Key point: The trap is calling await 'blocking'. It suspends the function while freeing the event loop, which is the opposite of blocking a thread.

Q18. What is a typedef in Dart?

A typedef gives a name to a type, most usefully a function type. Instead of writing bool Function(int) everywhere, you name it and reuse the name, which makes signatures readable. In modern Dart it also aliases any type, not just functions.

dart
typedef IntPredicate = bool Function(int);

List<int> where(List<int> items, IntPredicate test) =>
    items.where(test).toList();

where([1, 2, 3, 4], (n) => n.isEven);   // [2, 4]

Q19. What is the main function in Dart?

main is the entry point every Dart program runs first. It returns void (or Future<void> if it awaits) and can take an optional List<String> for command-line arguments. Execution starts here, and the app ends when main and any pending async work finish.

In a Flutter app, main is where you call runApp to boot the widget tree, so it's the one function you're guaranteed to write.

dart
void main(List<String> args) {
  print("Program starts here");
  if (args.isNotEmpty) print("First arg: ${args.first}");
}

Q20. What does the ??= operator do?

??= assigns a value to a variable only if that variable is currently null. a ??= b leaves a alone when it already has a value, and sets it to b otherwise. It's the one-line way to lazily set a default without an if check.

It pairs with nullable fields and caches: assign the computed value the first time, then reuse it.

dart
String? cached;
cached ??= computeName();   // sets it the first time
cached ??= computeName();   // skipped, already set
print(cached);

Q21. What are enums in Dart, and what are enhanced enums?

An enum defines a fixed set of named values, like Status.active. Dart's enhanced enums (from Dart 2.17) go further: an enum can have fields, a constructor, and methods, so each value carries data and behavior instead of just a name.

dart
enum Plan {
  lite(99),
  growth(499),
  pro(999);

  final int price;
  const Plan(this.price);

  bool get isPaid => price > 0;
}

print(Plan.growth.price);    // 499
print(Plan.pro.isPaid);      // true
Back to question list

Dart Intermediate Interview Questions

Intermediate19 questions

For candidates with working experience: language mechanics, async depth, and the questions that separate users from understanders.

Q22. What is the difference between a Future and a Stream?

A Future delivers a single asynchronous value once, then it's done. A Stream delivers a sequence of asynchronous events over time, and you listen to it, reacting to each one. Think Future for a one-off request and Stream for an ongoing feed.

You consume a Future with await; you consume a Stream with await for in an async function, or by calling .listen with a callback.

dart
Stream<int> countTo(int n) async* {
  for (var i = 1; i <= n; i++) {
    yield i;                 // emits each value
  }
}

void main() async {
  await for (var value in countTo(3)) {
    print(value);            // 1, 2, 3
  }
}
FutureStream
Values deliveredOneMany over time
Consumed withawaitawait for or .listen
Good forA single requestEvents, sockets, user input
Async producerasync / returnasync* / yield

Key point: Naming async* and yield for producing a Stream, alongside async and await for a Future, shows you know both directions.

Watch a deeper explanation

Video: Dart Futures - Flutter in Focus (Flutter, YouTube)

Q23. What is a mixin and how does it differ from inheritance?

A mixin is a bundle of methods and fields you add to a class with the with keyword, sharing behavior across classes that don't share a superclass. Because Dart allows only single inheritance, mixins are how you compose reusable behavior without deep class trees.

The difference: extends is an is-a relationship with one parent, while with layers in capabilities from several mixins. A class can mix in many, and their order matters because later mixins can override earlier ones.

dart
mixin Swimmer {
  void swim() => print("swimming");
}
mixin Walker {
  void walk() => print("walking");
}

class Duck with Swimmer, Walker {}

var d = Duck();
d.swim();
d.walk();

Key point: The follow-up is often 'mixin vs interface vs extends'. Have the one-line distinction ready for each.

Q24. What is the difference between abstract classes and interfaces in Dart?

An abstract class can't be instantiated and can hold both concrete and abstract methods, so it's for sharing partial implementation. Dart has no separate interface keyword: every class defines an implicit interface, and any class can implement another with the implements keyword, which forces it to provide every member.

So the choice is extends (inherit implementation) versus implements (adopt the contract only, write everything yourself).

dart
abstract class Shape {
  double area();               // no body, subclasses must define
  void describe() => print("Area is ${area()}");
}

class Circle extends Shape {
  final double r;
  Circle(this.r);
  @override
  double area() => 3.14159 * r * r;
}

Q25. What is a factory constructor and when do you use one?

A factory constructor is declared with the factory keyword and, unlike a normal constructor, doesn't have to create a fresh instance. It can return a cached object, an instance of a subtype, or an object built from complex logic. That flexibility is what enables caching, singletons, and parsing from JSON.

dart
class Logger {
  static final Map<String, Logger> _cache = {};
  final String name;

  Logger._internal(this.name);

  factory Logger(String name) {
    return _cache.putIfAbsent(name, () => Logger._internal(name));
  }
}

identical(Logger("app"), Logger("app"));   // true, same cached instance

Key point: The classic use the question needs is fromJson and caching. Mention that a factory can return null-free cached instances.

Q26. What is a const constructor and why does it matter?

A const constructor lets you create compile-time constant instances of a class, as long as all fields are final. Two const objects built with the same arguments are the identical object in memory, not just equal, which saves allocations.

In Flutter this matters for performance: a const widget is created once and skipped during rebuilds, so const constructors are a real optimization, not just style.

dart
class Point {
  final int x, y;
  const Point(this.x, this.y);   // const constructor
}

const a = Point(1, 2);
const b = Point(1, 2);
identical(a, b);   // true, canonicalized to one instance

Q27. How does error handling work in Dart?

Dart handles failures with try, catch, and finally. The catch clause can take one argument (the error) or two (the error and its stack trace), and an on clause narrows to a specific exception type so you handle each kind differently. You raise a failure with throw, and any finally block always runs for cleanup, whether or not something threw.

Dart splits failures into Error (bugs you shouldn't catch, like a failed assertion) and Exception (recoverable conditions). Catch Exceptions; let Errors surface so you fix the bug.

dart
try {
  var value = int.parse("abc");
} on FormatException catch (e) {
  print("bad number: ${e.message}");
} catch (e, stack) {
  print("other error: $e");
} finally {
  print("always runs");
}

Key point: Knowing the Error vs Exception distinction, and that you shouldn't catch Errors, indicates production experience.

Q28. How do generics work in Dart?

Generics let a class or function work over a type parameter, so List<int> and List<String> reuse one implementation with type safety. You declare a placeholder like <T> and the compiler checks usage, which catches type errors at compile time instead of runtime.

You can bound a type parameter with extends to require a capability, like <T extends num> so the code can do arithmetic on T.

dart
T firstOr<T>(List<T> items, T fallback) =>
    items.isEmpty ? fallback : items.first;

firstOr<int>([], -1);          // -1
firstOr(<String>["a"], "?");   // a

num total<T extends num>(List<T> xs) => xs.fold(0, (a, b) => a + b);

Q29. How do you handle errors from a Future?

With async/await you wrap the await in a normal try/catch, which reads cleanly. Without await, you attach .catchError to the Future, or pass onError to .then. A Future that fails and has no handler surfaces as an unhandled async error.

The practical guidance: prefer try/catch around await for readable flow, and always handle the failure path so errors don't vanish silently.

dart
Future<int> risky() async => throw StateError("boom");

void main() async {
  try {
    await risky();
  } catch (e) {
    print("caught: $e");
  }

  // callback style
  risky().catchError((e) => print("also caught: $e") as int);
}

Q30. Which Iterable methods should every Dart developer know?

map transforms each element, where filters, reduce and fold combine to a single value, any and every test conditions, and expand flattens. These are lazy on an Iterable, so nothing runs until you materialize with toList or iterate.

Reaching for these instead of manual loops is a strong fluency signal, and knowing they're lazy explains why you sometimes need a trailing .toList().

dart
var nums = [1, 2, 3, 4, 5];
var result = nums
    .where((n) => n.isOdd)
    .map((n) => n * n)
    .toList();
print(result);   // [1, 9, 25]

var total = nums.fold(0, (sum, n) => sum + n);   // 15

Q31. How do you implement value equality in Dart?

By default == compares identity: two objects are equal only if they're the same instance. To get value equality you override == and hashCode together, comparing the fields that define the object and hashing the same fields.

The rule interviewers check: if you override ==, you must override hashCode consistently, or the object misbehaves in Sets and Map keys. Many teams generate both with a package like equatable to avoid mistakes.

dart
class Money {
  final int amount;
  final String currency;
  const Money(this.amount, this.currency);

  @override
  bool operator ==(Object other) =>
      other is Money &&
      other.amount == amount &&
      other.currency == currency;

  @override
  int get hashCode => Object.hash(amount, currency);
}

Key point: Object.hash for combining fields is the modern idiom. Mentioning it beats hand-rolling amount.hashCode ^ currency.hashCode.

Q32. What are records in Dart 3?

A record is a lightweight, immutable bundle of values, with positional and named fields, written in parentheses like (1, name: 'Asha'). It's the clean way to return multiple values from a function without defining a class or abusing a List.

Records have value equality built in, so two records with the same fields are equal, and you can destructure them with patterns.

dart
(int, int) minMax(List<int> xs) {
  xs.sort();
  return (xs.first, xs.last);
}

var (lo, hi) = minMax([3, 1, 2]);
print("$lo..$hi");   // 1..3

({String name, int age}) person = (name: "Asha", age: 31);

Q33. What is pattern matching in Dart 3?

Patterns let you destructure and test values in one step, in switch cases, if-case statements, and variable declarations. A switch can match on shape, extract fields, and add guards with when, replacing chains of type checks and casts.

Combined with sealed classes, switch becomes exhaustive: the compiler errors if you forget a case, which is the safety win teams care about.

dart
sealed class Shape {}
class Circle extends Shape { final double r; Circle(this.r); }
class Square extends Shape { final double s; Square(this.s); }

double area(Shape shape) => switch (shape) {
  Circle(:var r) => 3.14159 * r * r,
  Square(:var s) => s * s,
};

Q34. What are sealed classes and why are they useful?

A sealed class can be extended only within its own file, so the compiler knows the complete set of subtypes. That completeness makes switch statements exhaustive: cover every subtype or get a compile error, which catches the bug where you add a new case and forget to handle it.

Sealed classes pair naturally with pattern matching to model a closed set of states, like the results of an operation.

dart
sealed class Result {}
class Ok extends Result { final String data; Ok(this.data); }
class Err extends Result { final String msg; Err(this.msg); }

String show(Result r) => switch (r) {
  Ok(:var data) => "ok: $data",
  Err(:var msg) => "error: $msg",
};   // exhaustive: no default needed

Q35. What are extension methods?

An extension adds methods or getters to an existing type you don't own, like String or int, without subclassing or wrapping it. The compiler resolves the extension statically, so it feels like the type gained a new method.

Use them to add small helpers to library types cleanly. The limitation to name: extensions are resolved by static type, so they don't participate in dynamic dispatch.

dart
extension StringExtras on String {
  String get reversed => split('').reversed.join();
  bool get isBlank => trim().isEmpty;
}

print("dart".reversed);   // trad
print("  ".isBlank);      // true

Q36. Why is a const collection different from a final collection?

A final list can't be reassigned, but its contents can still change: you can add to it. A const list is deeply immutable: the reference is fixed and any attempt to mutate it throws at runtime. const also canonicalizes, so two identical const lists are the same object.

So final protects the variable; const protects the whole structure.

dart
final a = [1, 2, 3];
a.add(4);              // ok, final only fixes the binding

const b = [1, 2, 3];
// b.add(4);           // runtime error: Unsupported operation

Key point: Candidates often think final means immutable. Correcting that (final fixes the binding, not the contents) is a quick depth signal.

Q37. What do the is, as, and as? operators do?

is tests whether an object has a type and returns a bool; is! is its negation. as casts an object to a type and throws if the object isn't that type. There's no as? operator; for a safe cast you combine is with a check or use pattern matching.

A useful detail: after a successful is check, Dart promotes the variable to that type inside the block, so you don't need a separate cast.

dart
Object value = "hello";

if (value is String) {
  print(value.length);   // promoted to String, no cast needed
}

var text = value as String;   // throws if not a String

Q38. What does the late keyword do and when is it appropriate?

late declares a non-nullable variable that you promise to initialize before use, deferring the assignment. It's for values you can't set at declaration but that are never legitimately null, like a field set in a Flutter initState. Accessing a late variable before it's assigned throws.

late also enables lazy initialization: late final x = expensive() runs expensive() only on first access.

dart
class Service {
  late final String token;   // set later, still non-nullable

  void init() => token = fetchToken();
}

late final expensive = compute();   // runs only when first read

Key point: The risk to acknowledge: late trades a compile-time null check for a runtime throw, so it's a promise you can break. Interviewers like hearing that trade-off.

Q39. What do the class modifiers final, base, and interface do in Dart 3?

Dart 3 added modifiers that constrain how a class can be used. final blocks both extending and implementing it outside its library. base forces subtypes to extend (not implement) so they inherit the implementation. interface allows implementing but not extending. They let a library author control its contract.

  • final: no extends and no implements outside the library.
  • base: subtypes must extend, not implement.
  • interface: other libraries can implement but not extend.
  • sealed: known, closed set of subtypes for exhaustive switches.
dart
final class Config {         // cannot be extended or implemented elsewhere
  final String env;
  Config(this.env);
}

base class Repository {}      // subtypes must extend, keeping the implementation

Q40. How do you serialize and deserialize JSON in Dart?

The dart:convert library gives jsonEncode to turn a Map or List into a JSON string and jsonDecode to parse a string back into Dart objects. jsonDecode returns dynamic (usually Map<String, dynamic>), so you map it into a typed model, commonly with a fromJson factory and a toJson method.

For larger apps, code generation (json_serializable) writes those fromJson and toJson methods for you, which avoids hand-written mapping bugs.

dart
import 'dart:convert';

class User {
  final String name;
  final int age;
  User(this.name, this.age);

  factory User.fromJson(Map<String, dynamic> j) =>
      User(j['name'] as String, j['age'] as int);
}

var user = User.fromJson(jsonDecode('{"name":"Asha","age":31}'));
Back to question list

Dart Interview Questions for Experienced Developers

Experienced20 questions

advanced rounds probe internals, concurrency, and design judgment. Expect every answer here to draw a follow-up.

Q41. What are isolates and how do they differ from threads?

An isolate is Dart's unit of concurrency: its own memory heap and its own event loop, running independently. Unlike threads, isolates share no memory, so there are no locks and no data races. They communicate only by passing messages over ports, and the message is copied.

This model is why Dart avoids a whole category of concurrency bugs, at the cost of not sharing objects directly. For heavy CPU work you spawn an isolate; for a one-off computation, Isolate.run is the short path.

dart
import 'dart:isolate';

int heavySum(int n) {
  var total = 0;
  for (var i = 0; i < n; i++) total += i;
  return total;
}

void main() async {
  var result = await Isolate.run(() => heavySum(100000000));
  print(result);   // runs off the main isolate, UI stays responsive
}

Key point: Say the phrase 'share nothing, message passing'. It's the one-line summary that signals you actually understand the model.

Watch a deeper explanation

Video: Isolates and Event Loops - Flutter in Focus (Flutter, YouTube)

Q42. How does Dart's event loop and single-threaded concurrency work?

Each isolate runs one thread with an event loop and two queues: a microtask queue and an event queue. The loop drains all microtasks first, then handles one event (like a completed I/O or a timer), then drains microtasks again. Because it's single-threaded, only one piece of Dart code runs at a time.

This is why a long synchronous computation freezes the UI: it hogs the loop. The fix is to break work up, await I/O, or move CPU work to another isolate.

  • Microtask queue: scheduled with scheduleMicrotask and completed Futures; drained fully before the next event.
  • Event queue: I/O, timers, gestures; one processed per loop turn.
  • await schedules the continuation as a microtask, so it resumes promptly.
  • A blocking loop starves both queues and stalls everything, including the frame.

Key point: Explaining microtask-before-event ordering is a senior-level distinction most candidates miss.

Q43. When do you use async/await versus an isolate?

Use async/await for I/O-bound waiting: network calls, file reads, timers. The work isn't running on the CPU, it's waiting, and the event loop stays free during the await, so one isolate handles thousands of concurrent I/O operations fine.

Reach for an isolate when the work is CPU-bound: parsing a huge JSON payload, image processing, heavy computation. That work would block the single event loop and drop frames, so you move it to a separate isolate that runs in parallel.

Work typeRight toolWhy
I/O-bound (network, disk)async / awaitEvent loop is free during the wait
CPU-bound (parsing, math)isolateRuns in parallel, keeps the loop responsive
Many concurrent waitsasync / awaitOne isolate handles thousands of awaits

Key point: The trap is reaching for isolates on I/O. Making clear that async already handles waiting is exactly what this question screens.

Watch a deeper explanation

Video: Async vs Isolates | Decoding Flutter (Flutter, YouTube)

Q44. What is the difference between JIT and AOT compilation in Dart?

Dart runs two ways. During development it uses a just-in-time compiler, which enables stateful hot reload: you change code and see it live without losing app state. For release it uses ahead-of-time compilation to native machine code, giving fast startup and predictable performance with no runtime compilation.

So you get the best of both: a fast edit loop while building, and a lean native binary when you ship. On the web, Dart instead compiles to JavaScript or WebAssembly.

Key point: Connecting JIT to hot reload and AOT to release performance is the answer interviewers hope to hear, not just the acronyms.

Q45. How does garbage collection work in Dart?

Dart uses a generational garbage collector. Most objects die young, so a fast young-space collector (a copying scavenger) reclaims short-lived objects cheaply, and survivors get promoted to old space, which a slower mark-sweep collector handles less often. The collector runs per isolate, and because isolates share no memory, collection never needs to stop other isolates.

The practical takeaway for UI work: Dart's GC is tuned to run in small steps that fit between frames, which is why Flutter apps stay smooth despite automatic memory management.

Q46. What is a StreamController, and what is the difference between single-subscription and broadcast streams?

A StreamController is the object you use to create a Stream and push events into it manually with add. A single-subscription stream allows exactly one listener and is the default, right for a sequence consumed once, like reading a file. A broadcast stream allows many simultaneous listeners, right for events like user actions that several parts of the app react to.

The gotcha to name: listening to a single-subscription stream twice throws, and a broadcast stream doesn't buffer events for late listeners, so they miss anything emitted before they subscribed.

dart
final controller = StreamController<int>.broadcast();

controller.stream.listen((v) => print("A: $v"));
controller.stream.listen((v) => print("B: $v"));

controller.add(1);   // both A and B receive 1
controller.close();

Q47. How does variance work with Dart generics?

Dart generics are covariant by default: List<int> is treated as a subtype of List<num> because int is a subtype of num. That's convenient but not fully sound for writes, so Dart inserts a runtime check on assignment into a covariantly-typed collection, which can throw if you store the wrong type.

The senior point: covariance is a deliberate ergonomics-versus-soundness trade-off, and the runtime check is how Dart keeps it safe. You can also mark a parameter covariant explicitly to override a method with a narrower type.

dart
List<num> nums = <int>[1, 2, 3];   // covariant: allowed
// nums.add(1.5);                   // runtime error: not an int
print(nums.first);                  // reading is always safe

Q48. What is dart:ffi and when would you use it?

dart:ffi (foreign function interface) lets Dart call directly into native C libraries: you load a shared library, describe the function signatures with typed bindings, and invoke them with low overhead. It's how you reuse an existing C or C++ library, or call platform APIs, without a plugin channel.

Reach for it when you need a mature native library or maximum performance for a specific routine. The cost is manual memory management across the boundary and platform-specific builds, so it's a targeted tool, not a default.

Q49. What are zones in Dart?

A zone is an execution context that can intercept and control asynchronous callbacks, uncaught errors, and even print. runZonedGuarded wraps code so any uncaught async error inside it lands in one handler, which is how apps install a top-level error reporter that catches failures from timers and futures that a plain try/catch would miss.

You rarely create zones by hand, but understanding them explains how error-tracking and testing frameworks capture async errors globally.

dart
import 'dart:async';

void main() {
  runZonedGuarded(() {
    Future.delayed(Duration.zero, () => throw StateError("async boom"));
  }, (error, stack) {
    print("reported: $error");   // catches the async error
  });
}

Q50. What is const canonicalization and why does it matter for performance?

When you build two const values with the same type and arguments, Dart creates only one object and hands out the same instance everywhere, which is canonicalization. That saves allocations and makes identity comparisons cheap.

In Flutter this is a real optimization: a const widget is canonicalized and its element can be reused across rebuilds instead of reconstructed, so marking widgets const where possible cuts work in the build phase.

Key point: Tying canonicalization to Flutter's const widget skip-rebuild behavior shows you language internals connects to real app performance.

Q51. How can a Dart application leak memory, and how would you diagnose it?

Even with garbage collection, memory grows when something keeps references alive: a Stream subscription that's never cancelled, a listener or timer that outlives its owner, a global cache that only grows, or a closure capturing a large object. In Flutter the classic case is not cancelling subscriptions and controllers in dispose.

Diagnosis: watch memory in DevTools, take heap snapshots and diff them over time to find the growing allocation, and trace retaining paths to see what holds the reference. The fix is almost always cancelling subscriptions, disposing controllers, and bounding caches.

  • Uncancelled StreamSubscriptions and Timers.
  • Controllers (animation, text, stream) not disposed in Flutter.
  • Unbounded caches and module-level growing collections.
  • Closures capturing large objects and outliving them.

Q52. How would you migrate a large pre-null-safety Dart codebase to sound null safety?

Incrementally, from the bottom up. Dart supported a mixed mode where null-safe and legacy libraries coexist, so you migrate leaf packages first, then packages that depend on them, keeping the app runnable throughout. The dart migrate tool proposes nullability annotations you review rather than accept blindly.

The judgment interviewers probe: resist sprinkling ! to silence the analyzer, because that reintroduces exactly the runtime crashes null safety prevents. Prefer real nullable types, ?? defaults, and required parameters.

Bottom-up null safety migration

1Migrate leaf packages
start with packages that depend on nothing else
2Move up the graph
migrate dependents once their dependencies are null-safe
3Run dart migrate
review proposed nullable annotations, don't accept blindly
4Fix real nullability
use ?? and required, not ! to silence the analyzer

Mixed mode lets null-safe and legacy libraries coexist, so the app stays runnable throughout.

Key point: Naming the anti-pattern (spamming ! to make errors go away) is what separates a migration you've done from one you've read about.

Q53. What does the on clause in a mixin declaration do?

on restricts which classes can use a mixin: mixin M on Base means only classes that extend Base can mix in M. In return, the mixin gets to call members of Base and use super to reach the superclass's implementation. It's how a mixin declares a dependency on the behavior it augments.

dart
class Widget {
  void render() => print("base render");
}

mixin Logging on Widget {
  @override
  void render() {
    print("before");
    super.render();   // allowed because of 'on Widget'
  }
}

class Button extends Widget with Logging {}

Q54. What is a Completer and when do you use one?

A Completer gives you a Future whose completion you control by hand: you hold the Completer, hand out its .future, and later call complete(value) or completeError(error). It bridges callback-based or event-based APIs that don't already return Futures into the async/await world.

The rule to state: complete a Completer exactly once, and guard against completing an already-completed one. When an API already returns a Future, you don't need a Completer, so reaching for one on plain async code is a smell.

dart
import 'dart:async';

Future<String> waitForCallback() {
  final completer = Completer<String>();
  legacyApi(onDone: (result) => completer.complete(result));
  return completer.future;
}

Q55. How do you write and structure tests in Dart?

The test package is the standard: test files end in _test.dart, test() holds a single case with expect() assertions, and group() organizes related cases. setUp and tearDown handle per-test fixtures. You run everything with dart test, and mocktail or mockito fake out external boundaries.

A strong answer covers testing behavior over implementation, using async matchers like expectLater for streams and futures, and isolating I/O so tests stay fast and deterministic.

dart
import 'package:test/test.dart';

int discount(int price, int pct) => price - (price * pct ~/ 100);

void main() {
  group('discount', () {
    test('applies percent', () => expect(discount(100, 10), 90));
    test('zero percent', () => expect(discount(100, 0), 100));
  });
}

Q56. How does operator overloading work in Dart?

Dart lets a class define what operators like +, -, ==, [], and < mean for its instances by declaring them with the operator keyword. This is how value types read naturally: adding two Money objects or comparing two Vectors looks like arithmetic instead of method calls.

The discipline to mention: overload only when the operator's meaning is obvious, and keep == consistent with hashCode. A surprising + is worse than a plain method.

dart
class Vector {
  final double x, y;
  const Vector(this.x, this.y);

  Vector operator +(Vector o) => Vector(x + o.x, y + o.y);
  Vector operator *(double s) => Vector(x * s, y * s);
}

var v = Vector(1, 2) + Vector(3, 4);   // (4, 6)

Q57. What are async generators (async*) and sync generators (sync*)?

A generator function produces a sequence lazily with yield. sync* returns an Iterable and computes values on demand as you iterate. async* returns a Stream and can await between yields, so it emits values over time. yield* delegates to another generator, flattening it in.

Use sync* for lazy synchronous sequences and async* when producing a Stream from asynchronous steps, like paging through an API.

dart
Iterable<int> naturals(int n) sync* {
  for (var i = 1; i <= n; i++) yield i;
}

Stream<int> ticks(int n) async* {
  for (var i = 1; i <= n; i++) {
    await Future.delayed(Duration(milliseconds: 100));
    yield i;
  }
}

Q58. Design question: how would you build an in-memory cache with expiry in Dart?

Clarify first: bounded size, time-based expiry, or both? Then model an entry as a value plus an insertion timestamp, stored in a Map keyed by the request. On read, check whether the entry exists and is still within its TTL; if it's stale, evict and treat it as a miss. For size bounds, track access order and evict the least-recently-used entry when full.

The closing step is the operational edges: making it generic over key and value types, guarding concurrent access if it's touched from multiple async paths, and a Timer or lazy check to purge expired entries. Structuring the answer clarify, data model, read/write paths, eviction is what the question checks.

dart
class TtlCache<K, V> {
  final Duration ttl;
  final _store = <K, (V, DateTime)>{};
  TtlCache(this.ttl);

  V? get(K key) {
    final entry = _store[key];
    if (entry == null) return null;
    if (DateTime.now().difference(entry.$2) > ttl) {
      _store.remove(key);
      return null;
    }
    return entry.$1;
  }

  void set(K key, V value) => _store[key] = (value, DateTime.now());
}

Structuring the cache design answer

1Clarify requirements
bounded size, time-based expiry, or both
2Model the entry
value plus insertion timestamp in a Map
3Define read and write
on read, evict if past TTL and return a miss
4Handle eviction
least-recently-used when size-bounded, plus edge cases

the scoping and expiry logic more than the exact API you land on is the technical point.

Key point: how you scope requirements and handle expiry, not the exact API is the technical point.

Q59. How do you run multiple Futures concurrently and handle their results?

Future.wait takes a list of Futures, starts them all, and returns a single Future that completes with the list of results once every one finishes. It's how you fan out independent I/O and wait for the whole batch instead of awaiting each in series.

The behavior to name: by default Future.wait fails fast, if any Future throws, the returned Future errors, though the others keep running. When you need every result including failures, use eagerError control or wrap each Future so one failure doesn't sink the batch.

dart
Future<int> fetch(int id) async {
  await Future.delayed(Duration(milliseconds: 100));
  return id * 10;
}

void main() async {
  var results = await Future.wait([fetch(1), fetch(2), fetch(3)]);
  print(results);   // [10, 20, 30], all ran concurrently
}

Key point: Contrasting Future.wait (parallel) with a for loop of awaits (serial) is the distinction interviewers probe for latency understanding.

Q60. What is metadata (annotations) in Dart and how is it used?

Annotations are metadata you attach with @, like @override, @deprecated, and @pragma. Some are advisory to the analyzer (@override flags a typo in a method name), and custom annotations become inputs to code generation, where a build step reads them and emits code.

That's the pattern behind json_serializable and similar tools: you annotate a class, and a generator produces the boilerplate. Annotations themselves do nothing at runtime unless a tool or reflection reads them.

dart
class Base {
  void update() {}
}

class Child extends Base {
  @override
  void update() {}   // analyzer errors if update() doesn't exist in Base

  @deprecated
  void oldMethod() {}
}
Back to question list

Why Dart? Dart vs Kotlin, JavaScript, and Swift

Dart wins when you want one codebase across mobile, web, and desktop with a fast, predictable UI, which is exactly the Flutter promise. It compiles ahead of time to native code for release builds and just in time during development, so you get both quick startup in production and stateful hot reload while you work. Where it competes is against the platform-native and cross-platform options a team already knows: Kotlin, Swift, and JavaScript. Knowing these trade-offs out loud is itself an interview signal, because it shows you pick tools on merits, not habit.

LanguageTypingBest atWatch out for
DartStatic, sound null safetyFlutter cross-platform apps, one codebaseSmaller ecosystem outside Flutter
KotlinStaticNative Android, JVM backendsiOS story needs KMP, less mature UI sharing
JavaScriptDynamicWeb and full-stack, huge ecosystemType quirks, no sound null safety
SwiftStaticNative iOS and Apple platformsNot cross-platform outside Apple

How to Prepare for a Dart Interview

Prepare in layers, and practice out loud. Most Dart rounds move from concept questions to live coding to a Flutter or design discussion, so rehearse each stage rather than only reading answers.

  • Master your tier's concepts until you can explain them without notes, then read one tier up for the stretch questions.
  • Type and run every snippet on DartPad; modifying working code cements it far faster than reading.
  • Practice thinking aloud on small problems with a timer, because your process, not just the final answer is the technical point.
  • Take the quiz on this page to find weak spots, then revisit those questions before the real round.

The typical Dart interview flow

1Recruiter or phone screen
background, motivation, a few concept checks
2Language concepts
null safety, async/await, mixins, constructors, collections
3Live coding
write and explain a function or small widget under observation
4Flutter or design
state management, trade-offs, reading unfamiliar code, follow-ups

Earlier rounds increasingly run as AI coding interviews. The concepts on this page are what gets probed at every stage.

Test Yourself: Dart Quiz

Ready to test your Dart knowledge?

10 questions, about 6 minutes. Score 70% or higher to earn a shareable certificate.

10 questions Instant feedback Free certificate on 70%+

Frequently  Asked  Questions

Which Dart topics should I prioritize before a technical round?

Prioritize the areas that change real decisions: fundamentals, practical tasks, debugging, trade-offs, and evidence. The highest-value questions are tied to a project example, a validation check, or a failure mode.

Are Dart and Flutter interviews the same thing?

They overlap but aren't identical. A Dart interview tests the language: null safety, futures, mixins, generics. A Flutter interview adds widgets, state management, and the render pipeline on top. Most hiring loops mix both, so prepare the Dart fundamentals here and layer Flutter specifics if the role is mobile.

Which Dart version do these answers assume?

Dart 3, throughout. Dart 3 made sound null safety mandatory and added records, patterns, and sealed classes, all covered here where relevant. If an interviewer asks about older behavior, it's usually the pre-3.0 optional null safety, and you can say you're answering for Dart 3.

How long does it take to prepare for a Dart interview?

If you use Dart or Flutter at work or in study, one to two weeks of an hour a day covers this bank with practice runs. Starting colder, plan three to four weeks and write code daily on DartPad; reading answers without typing them is how preparation quietly fails.

Do interviewers ask exactly these questions?

The concepts repeat far more reliably than the exact wording. Interviewers rephrase, chain follow-ups, and push toward whatever you claim confidence in. Prepare the underlying ideas, null safety, async, mixins, constructors, and the phrasing takes care of itself.

Is there a way to test my Dart knowledge?

Yes. The quiz on this page scores you as you go and explains every answer, so you find your weak spots fast. Pass the threshold and you can download a shareable certificate with your name and score, free and with no sign-up. It's the quickest way to check yourself before the real round.

From the team that builds coding interviews

Hyring builds the AI Coding Interviewer that runs live technical rounds in 20+ languages for 5,000+ hiring teams. These Dart questions and scoring notes reflect what actually gets asked and evaluated inside the interviews we host.

See how the AI Coding Interviewer works

Sources

Adithyan RKWritten by Adithyan RK
Surya N
Fact-checked by Surya N
Published on: 11 May 2026Last updated: 4 Jul 2026
Share: