What is cascade notation (..)?
2 min read
Dart Syntax
| Operator | Returns | Use when |
|---|---|---|
. | result of the call | Normal method invocation |
.. | the receiver (the object itself) | Configure / mutate same object multiple times |
?.. | receiver or null | Receiver might be null; first segment must be ?.., rest can stay .. |
The .. is syntactic. It's not method chaining via return-this — it works on any method/field, including ones that return void.
Code in action
// Without cascade — repetitive
final btn = Button();
btn.text = 'Click me';
btn.color = Colors.blue;
btn.onTap = handleTap;
btn.build();
// With cascade — same result, less noise
final btn = Button()
..text = 'Click me'
..color = Colors.blue
..onTap = handleTap
..build();
// Why it matters: works with void-returning methods
final list = [1, 2]..add(3); // [1, 2, 3]
[1, 2].add(3); // returns void — list is unreachable
final buf = StringBuffer()
..write('Hello, ')
..write('world')
..writeln('!');
print(buf.toString()); // "Hello, world!\n"
// Painters love it
final paint = Paint()
..color = Colors.indigo
..strokeWidth = 2
..style = PaintingStyle.stroke;
Null-aware cascade ?..
User? user = maybeUser();
user
?..name = 'Alice' // first segment is null-aware
..age = 30; // subsequent ones stay `..` — already non-null
// Equivalent to:
if (user != null) {
user.name = 'Alice';
user.age = 30;
}
The trick: only the first cascade needs ?... Once the chain enters, Dart already knows the receiver is non-null.
When to use cascades
| Situation | Use |
|---|---|
Configure a builder-style object (Paint, Path, Form) | ✅ Cascade |
| Initialise a collection with several mutations | ✅ Cascade |
| Set a handful of properties on a freshly-constructed object | ✅ Cascade |
| You actually want the result of the call, not the receiver | ❌ Use . |
Chain of different objects (a.b.c.d) | ❌ Not cascades — that's normal chaining |
Common mistakes to avoid
// ❌ Expecting cascade to return the call result
final length = [1, 2, 3]..length; // returns the list, NOT 3
// ✅ Use a normal access
final length = [1, 2, 3].length;
// ❌ Stray semicolon kills the chain
final paint = Paint()
..color = Colors.red;
..strokeWidth = 2; // ❌ standalone statement now
// ❌ Trying to call a getter through cascade and reuse it
final user = User()..name; // result is the User, not the name
// ❌ Mixing `..` and `.` on the same step
final x = Paint()..color.red; // ❌ accesses Color.red on color — usually a mistake
// ❌ Forgetting `?..` for nullable receivers
User? u = maybe();
u..name = 'Alice'; // 💥 if u is null
// ✅ u?..name = 'Alice';
Interview follow-ups
-
What does the cascade operator return? The receiver, regardless of what the called method returns. That's why
[1, 2]..add(3)evaluates to the list — even thoughaddreturns void. -
How is cascade different from method chaining via
return this? Method chaining only works when every method explicitly returnsthis, and you're tied to its return type. Cascade is syntactic: it works on any method or property — even void ones, even ones returning unrelated values. No API design tax required. -
When would you use
?..vs..??..when the receiver might be null. Only the first cascade needs the?— once you're past it, Dart treats subsequent calls as non-null. -
Can you nest cascades? Yes, with parentheses:
obj..a.list..add(1)is a stylistic trap. Use..to address the same receiver and parentheses to scope nested cascades on inner objects — but at that point, splitting into two statements is usually clearer.
How helpful was this content?
Please sign in to rate this article.