Live Coding Round in Dart
Debounce, Result types, LRU cache, backoff, concurrency limits, tries, stream transformers
- 01
A search field fires a request per keystroke and you cannot add rxdart — how do you write debounce and throttle, and which one loses the last keystroke?
EasyDebounce waits for the burst to end and runs only the last call; a leading-edge throttle runs the first call and silently drops everything after it, including the last one.
import 'dart:async'; class Debouncer { Debouncer(this.delay); final Duration delay; Timer? _timer; … - 02
The API returns a flat list of transactions and the design wants them under date headers — how do you group them, and what makes the DateTime key betray you?
EasyFold the flat list into a
Map<DateTime, List<Txn>>keyed by the date with the time stripped, then sort the keys — the grouping is one pass and about five lines.import 'package:collection/collection.dart'; // groupBy typedef Section = ({DateTime day, List<Txn> items}); DateTime _dayOf(DateTime t) { final local = t.toLocal(); // the server sends UTC … - 03
Every caller of your repository wraps it in try/catch and half of them forget — how would you write a Result<T, E> with map, mapError and fold instead?
MediumA
sealed class Result<T, E>withOkandErrsubtypes moves failure into the return type, so the compiler — not code review — is what forces every caller to deal with it.sealed class Result<T, E> { const Result(); R fold<R>(R Function(T value) onOk, R Function(E error) onErr) => switch (this) { Ok(:final value) => onOk(value), Err(:final error) => onErr(error), … - 04
Your cache holds messages sorted by time and the server returns another sorted page — how do you merge them and drop duplicate ids in one pass?
MediumWalk both lists with two indices, always taking the smaller head, and keep a
Set<String>of ids already emitted: O(n + m) time, one pass, no second sort.List<Message> mergeSorted(List<Message> cached, List<Message> fresh) { final out = <Message>[]; final seen = <String>{}; var i = 0, j = 0; void take(Message m) { … - 05
A comment thread arrives as a nested tree and ListView.builder needs a flat list with indentation — how do you flatten it without recursion?
MediumPre-order DFS with an explicit stack: push the roots reversed, then pop a node, emit
(comment, depth), and push its children reversed so they come back out in reading order.import 'dart:math'; import 'package:collection/collection.dart'; class Comment { Comment(this.id, this.text, {this.children = const []}); final String id; … - 06
A pricing call takes 40 ms and the same two arguments come back on every rebuild — how do you memoize it, and what makes a two-argument cache key actually hit?
MediumWrap the function in a closure over a
Mapand use a Dart record(a, b)as the key — aListkey never hits, becauseListequality is identity.R Function(A) memo1<A, R>(R Function(A) f) { final cache = <A, R>{}; return (a) => cache.putIfAbsent(a, () => f(a)); // one call per missing key } R Function(A, B) memo2<A, B, R>(R Function(A, B) f) { … - 07
Your image-metadata cache grows until the OS kills the app — how do you write an LRU bounded at 100 entries, and what do get, put and eviction cost?
MediumA Dart
Mapis aLinkedHashMapthat preserves insertion order, so an LRU is that map plus two moves: on a hit remove the key and re-insert it, and on an insert past capacity removekeys.first.class LruCache<K, V> { LruCache(this.capacity) : assert(capacity > 0); final int capacity; final _entries = <K, V>{}; // LinkedHashMap: insertion order == recency order … - 08
Three widgets ask for the same profile in the same frame and you fire three identical requests — how do you collapse that into one call?
MediumKeep a
Map<String, Future<T>>of the requests currently in flight and hand every later caller the sameFuture— a DartFutureis a value you can await any number of times, and the work behind it runs once.class RequestCoalescer { final _inFlight = <String, Future<dynamic>>{}; Future<T> run<T>(String key, Future<T> Function() start) { final running = _inFlight[key]; if (running != null) return running.then((value) => value as T); … - 09
You need to fetch 50 product details with at most four requests in flight and the results in the original order — how do you write that scheduler?
MediumOrder is the free half —
Future.waitreturns results in argument order no matter who finishes first; the work is bounding how many run at once, and that needs a pool fed with functions, not with futures that have already started.import 'package:collection/collection.dart'; import 'package:pool/pool.dart'; /// Runs [task] over [items] with at most [limit] in flight, results in order. Future<List<R>> mapPooled<T, R>( List<T> items, … - 10
Write a typed event bus with subscribe and unsubscribe — what keeps a listener alive after the screen that registered it is gone?
MediumType the bus by event class and hand the caller back a
StreamSubscriptionit has to cancel — a bus keyed by strings that storesvoid Function(Object)callbacks has no way of knowing a screen is gone.import 'dart:async'; class EventBus { final _ctrl = StreamController<Object>.broadcast(); void emit(Object event) { … - 11
Write a paging controller — cursor, in-flight guard, end-of-list flag, retry. What happens if the user pulls to refresh while page three is still loading?
HardThe four fields are the easy part; the bug is that page three's response can land after the refresh has already reset the list, so the controller needs a generation counter that turns every in-flight response into a no-op.
import 'package:flutter/foundation.dart'; class Page<T> { Page(this.items, this.nextCursor); final List<T> items; final String? nextCursor; // null means: that was the last page … - 12
Implement "at most five uploads per rolling minute" — and how do you test it without a suite that sleeps for a minute?
HardKeep a queue of the timestamps you have already granted: a call is admitted immediately while fewer than N of them fall inside the window, otherwise it is scheduled for the moment the oldest one ages out.
import 'dart:async'; import 'dart:collection'; import 'package:clock/clock.dart'; import 'package:fake_async/fake_async.dart'; import 'package:test/test.dart'; … - 13
How would you write a custom Iterable — a sliding window over a list — and why is sync* usually the better answer?
HardAn
Iterableis a factory forIterators: you implementget iterator, and the whole contract is that every call returns a fresh iterator positioned before the first element.import 'dart:collection'; // ── Hand-rolled: the state machine written out ── class Windows<E> extends IterableBase<List<E>> { Windows(this._source, this.size); final List<E> _source; … - 14
A GPS stream fires ten fixes a second — how do you write a transformer that emits at most one every five seconds and drops anything within ten metres of the last one kept?
HardBuild it as a
StreamTransformer.fromBindover anasync*function, so the filter state — the last fix you kept — is created fresh on everybind, and gate on the fix's own timestamp rather than on the device clock.import 'dart:async'; import 'dart:math' as math; class Fix { const Fix(this.lat, this.lon, this.at); final double lat; … - 15
A test comparing two decoded JSON maps fails with 400 lines of output — how do you write a deep compare that reports the first differing path instead of a bool?
HardWalk both structures together carrying a path, and return the first mismatch as a string like
$.user.tags[1]instead of a bool — a plain==on two maps compares identity, which is the only reason this function has to exist at all.import 'package:collection/collection.dart'; import 'package:test/test.dart'; /// Returns null when the two structures match, else the first differing path. String? firstDiff(Object? expected, Object? actual, [String path = r'$']) { if (expected is Map && actual is Map) { … - 16
How would you build offline autocomplete over 50 000 product names with a trie, and how do you justify it against a plain startsWith scan?
HardA trie makes the prefix lookup O(len(prefix)) plus the cost of collecting matches, but for 50 000 short names a sorted list with two binary searches beats it on memory and on build time — the trie earns its place when you need payloads in the nodes, ranked completions or fuzzy walks.
import 'package:collection/collection.dart'; class TrieNode { final Map<int, TrieNode> children = {}; // keyed by code unit final List<int> ids = []; // product ids whose name ends here } …