Available for work
Ankit Ranjan
Back to Deep Dives

Maps in Dart — Key-Value Mastery, Performance, and Patterns

A deep dive into Dart Maps: how key-value lookup works, the different Map implementations, common patterns, and AI prompts to help you choose the right data structure.

May 27, 2026 8 topics 7 quiz questions
Share:
1

Why Maps exist — associating data by identity

Lists let you access items by position (list[0]), but what if you need to access by identity — a user ID, a product SKU, a configuration key? You could search through a List, but that's O(n). Maps solve this elegantly.

Map — Key-Value Association with O(1) Access'user_123'keyhashCode→ bucket 5Hash Table Buckets[0] null[1] null[2] null[3] null[4] null[5]Entrykey: 'user_123'value: User(...)next: null[6] null[7] nullList Approach (index-based)users[0], users[1], users[2]Must remember index, no semantic meaningMap Approach (key-based)users['user_123']Meaningful keys, O(1) lookup by identityMap = Set of key-value pairs where keys are uniqueSame O(1) hash table performance, but associates each key with a value

A Map is essentially a Set of key-value pairs, where each key is unique. The hash table gives you O(1) lookup, insertion, and deletion — by key.

// List — must search to find user
var users = [user1, user2, user3];
var found = users.firstWhere((u) => u.id == 'user_123');  // O(n)

// Map — direct lookup by key
var users = {'user_123': user1, 'user_456': user2};
var found = users['user_123'];  // O(1)

2

Map implementations — LinkedHashMap, HashMap, SplayTreeMap

Like Set, Dart offers multiple Map implementations. And like Set, the default might surprise you.

Dart Map Types — Choosing the Right ImplementationTypeOrderLookupBest ForCreateLinkedHashMap(default)Insertion orderO(1)General purpose,JSON, configs{k: v}HashMap(dart:collection)UnorderedO(1)Slightly less memorywhen order irrelevantHashMap()SplayTreeMap(dart:collection)Sorted by keyO(log n)Sorted iteration,range queriesSplayTreeMap()Unmodifiable(wrapper)Preserves baseSame as baseSafe API returns,defensive copiesMap.unmodifiable(map)Like Set, Dart's default Map is LinkedHashMap — it maintains insertion order!

import 'dart:collection';

// Default — preserves insertion order
var linked = {'c': 1, 'a': 2, 'b': 3};
print(linked.keys);  // (c, a, b)

// Pure hash — no order guarantee
var hash = HashMap<String, int>()..addAll({'c': 1, 'a': 2, 'b': 3});

// Sorted by key
var sorted = SplayTreeMap<String, int>()..addAll({'c': 1, 'a': 2, 'b': 3});
print(sorted.keys);  // (a, b, c)

3

Common Map patterns — lookup tables, caching, counting

Maps are incredibly versatile. Here are the patterns you'll use most often.

Common Map Patterns in DartLookup Tableconst errors = {404: 'Not Found',500: 'Server Error',};Countingvar counts = <String, int>{};for (var word in words) {counts[word] =(counts[word] ?? 0) + 1;Memoizationfinal _cache = <int, int>{};int fib(int n) =>_cache[n] ??=fib(n-1) + fib(n-2);Entity Storevar users =<String, User>{};users[user.id] = user;// O(1) by ID lookupNested ConfigMap<String, dynamic>config = jsonDecode(...);var host =config['server']['host'];Multi-key Indexvar byName =<String, User>{};var byEmail =<String, User>{};Useful Map Operationsmap[key] // Get value (null if missing)map[key] = value // Set value (overwrite if exists)map.putIfAbsent(k, () => v)// Set only if missingmap.update(k, (v) => v+1)// Update existingmap.entries, map.keys, map.values// Iterate over pairs, keys only, or values only

The null-aware assignment trick:

// Common pattern: compute expensive value only once
var cache = <String, Data>{};

Data getData(String key) {
  return cache[key] ??= expensiveCompute(key);
  // ??= means: if null, compute and assign
}

4

Map gotchas — null values, missing keys, and empty {}

Maps have a few surprises that catch developers off guard.

Gotcha 1: Empty {} creates a Map, not a Set

var oops = {};  // Map<dynamic, dynamic>, NOT Set!
var set = <String>{};  // This is a Set
var map = <String, int>{};  // This is a Map
Gotcha 2: map[key] returns null for missing keys
var map = {'a': 1};
print(map['b']);  // null, not an error!

// Use containsKey() to distinguish
if (map.containsKey('b')) {
  // Key exists (value might be null)
} else {
  // Key doesn't exist
}
Gotcha 3: Null values are valid
var map = {'a': null};
print(map['a']);  // null
print(map['b']);  // also null!

// These are different situations:
print(map.containsKey('a'));  // true
print(map.containsKey('b'));  // false
Gotcha 4: Modifying during iteration throws
var map = {'a': 1, 'b': 2, 'c': 3};

// BAD — ConcurrentModificationError
for (var key in map.keys) {
  if (map[key] == 2) map.remove(key);  // throws!
}

// GOOD — collect keys first, then remove
var toRemove = map.keys.where((k) => map[k] == 2).toList();
toRemove.forEach(map.remove);

5

Transforming Maps — map, where, and conversion

Maps support functional transformations, but the syntax differs slightly from Lists.

var prices = {'apple': 1.20, 'banana': 0.80, 'cherry': 2.50};

// Transform values
var doubled = prices.map((k, v) => MapEntry(k, v * 2));
// {'apple': 2.40, 'banana': 1.60, 'cherry': 5.00}

// Filter entries
var expensive = Map.fromEntries(
  prices.entries.where((e) => e.value > 1.0)
);
// {'apple': 1.20, 'cherry': 2.50}

// Swap keys and values
var inverted = prices.map((k, v) => MapEntry(v, k));
// {1.20: 'apple', 0.80: 'banana', 2.50: 'cherry'}

// Convert to List of pairs
var pairs = prices.entries.toList();
// [MapEntry(apple: 1.20), MapEntry(banana: 0.80), ...]
Grouping data:
var words = ['apple', 'banana', 'apricot', 'blueberry'];

// Group by first letter
var grouped = <String, List<String>>{};
for (var word in words) {
  (grouped[word[0]] ??= []).add(word);
}
// {'a': ['apple', 'apricot'], 'b': ['banana', 'blueberry']}

6

Performance considerations

Maps share the same hash table performance characteristics as Sets.

Operation complexity:
- map[key] — O(1) average
- map[key] = value — O(1) amortised
- map.remove(key) — O(1) average
- map.containsKey(key) — O(1) average
- map.containsValue(value) — O(n) (must scan all values!)
- Iteration — O(n)

Key insight: containsValue() is O(n) — it can't use hashing because values aren't indexed. If you need fast value lookup, maintain a reverse Map.

// Slow: O(n) value lookup
var userById = <String, User>{};
var found = userById.values.firstWhere((u) => u.email == email);

// Fast: O(1) with reverse index
var userById = <String, User>{};
var userByEmail = <String, User>{};

void addUser(User u) {
  userById[u.id] = u;
  userByEmail[u.email] = u;  // reverse index
}

var found = userByEmail[email];  // O(1)

7

Maps and parallel computation

Like other collections, Maps can be processed in Isolates for CPU-intensive operations.

import 'dart:isolate';

// Process large Map in parallel
Future<Map<String, int>> processInParallel(
  Map<String, String> data,
) async {
  return await Isolate.run(() {
    // Heavy computation in separate isolate
    return data.map((k, v) => MapEntry(k, expensiveHash(v)));
  });
}

// Merge results from multiple isolates
Future<Map<String, int>> parallelCount(List<String> words) async {
  // Split into chunks
  var chunks = splitIntoChunks(words, 4);
  
  // Process chunks in parallel
  var results = await Future.wait(
    chunks.map((chunk) => Isolate.run(() => countWords(chunk)))
  );
  
  // Merge results
  return mergeCountMaps(results);
}
When to parallelise Map operations:
- Large Maps (>10k entries)
- Expensive per-entry computations
- Bulk transformations

When NOT to parallelise:
- Simple lookups (already O(1))
- Small Maps (overhead exceeds benefit)

8

AI prompts — choosing the right Map strategy

Use these prompts with AI assistants to help choose optimal Map strategies for your use case.

Prompt 1 — Data structure selection:

I need to store [describe your data] in Dart.
Access patterns:
- [how you access: by ID, by name, by multiple keys]
- Frequency: [read-heavy, write-heavy, balanced]
- Size: [approximate number of entries]

Should I use Map, List, or multiple data structures?
If Map, which implementation? Do I need reverse indexes?
Prompt 2 — JSON handling:
I'm working with this JSON structure in Dart:
[paste your JSON or describe structure]

Help me:
1. Define proper Dart types (typed Maps vs classes)
2. Handle nullable values safely
3. Write type-safe accessors
4. Consider using code generation (json_serializable)?
Prompt 3 — Caching strategy:
I need to cache [describe what you're caching] in Dart.

Requirements:
- Cache size: [bounded/unbounded]
- Eviction policy: [LRU/TTL/none]
- Thread safety: [single isolate/multiple]

Design a Map-based caching solution with:
- Proper key design
- Memory management
- Cache invalidation strategy
Prompt 4 — Migration from dynamic Map:
Here is my Dart code using Map<String, dynamic>:
[paste your code]

Refactor this to be more type-safe. Consider:
- Creating model classes
- Using proper generic types
- Handling missing keys gracefully
- Adding null safety

Maps in Dart

Maps are the key-value workhorses of Dart development. This episode explores how they achieve O(1) lookup, common patterns from caching to counting, and the gotchas that trip up developers.

Maps Deep Dive Quiz

7 questions

Test your understanding of Dart Maps, hash tables, and patterns

Search

Loading search...