Dart 17 🎯 Core Collections: Map Operations, Entries, and Iteration
A Map is a collection of key-value pairs. Each key is unique, and each key maps to exactly one value. The map is the collection for lookups: given a key, retrieve the value. Where a List is indexed by position and a Set is indexed by membership, a Map is indexed by a key of the developer’s choosing. The key can be a string, a number, an enum, or any object with proper == and hashCode implementations. The value can be anything.
The default Map implementation is LinkedHashMap, which preserves insertion order. This is the same choice Dart made for Set, and for the same reason: predictable iteration is usually what application code wants. A HashMap trades ordering for a small performance gain, and a SplayTreeMap sorts its keys by their natural order or by a comparator. The three implementations share the same interface and differ in ordering and performance.
This chapter covers map creation, key access and assignment, the putIfAbsent and update methods, iteration over keys, values, and entries, the map, where, and forEach methods, and the patterns for using maps as lookups, counters, and caches.
Key point: A Map is a collection of unique keys mapped to values. The default Map is a LinkedHashMap, which preserves insertion order. Access a value with map[key], which returns null if the key is absent. Iterate with .keys, .values, or .entries. Use putIfAbsent for lazy initialization and update for conditional updates.
Why Map exists
The lookup problem. Some data is naturally a lookup: a user ID maps to a user object, a country code maps to a country name, a configuration key maps to a value. A list would require a linear scan to find the entry; a map provides a hash lookup, which is constant time on average.
The uniqueness problem. Keys must be unique. Adding a value with an existing key replaces the previous value. This is the correct semantics for a lookup table: the key identifies the entry.
The named-access problem. A list is indexed by position, which is meaningful only when the order matters. A map is indexed by a key, which is meaningful regardless of order. The key is the identity of the entry.
The iteration problem. Iterating a map means iterating its keys, its values, or its entries. The three views serve different purposes: keys for operations that need only the identifiers, values for operations that need only the data, and entries for operations that need both.
The ordering problem. Some maps need predictable iteration order, and some do not. The LinkedHashMap preserves insertion order, the HashMap does not, and the SplayTreeMap sorts. The three implementations let the developer choose.
a. Creating maps
A map literal uses curly braces with key: value pairs.
var capitals = {
'Norway': 'Oslo',
'Sweden': 'Stockholm',
'Denmark': 'Copenhagen',
};
The keys and values are typed. An explicit type can be given:
var scores = <String, int>{
'Alice': 95,
'Bob': 87,
};
An empty map requires a type to distinguish it from an empty set:
var empty = <String, int>{};
A map can be created from two parallel lists:
var keys = ['a', 'b', 'c'];
var values = [1, 2, 3];
var map = Map.fromIterables(keys, values);
// {a: 1, b: 2, c: 3}
A map can be created from an iterable of entries:
var entries = [MapEntry('a', 1), MapEntry('b', 2)];
var map = Map.fromEntries(entries);
The Map.fromEntries constructor is useful when the entries come from another map or from a transformation.
b. Accessing and assigning
A value is accessed with the bracket syntax and the key.
var capitals = {'Norway': 'Oslo'};
print(capitals['Norway']); // Oslo
print(capitals['France']); // null
The bracket syntax returns null if the key is absent. The return type is nullable (String? for a Map<String, String>), which forces the caller to handle the missing case.
The containsKey method checks whether a key exists:
if (capitals.containsKey('Norway')) { }
The containsValue method checks whether a value exists, but it is a linear scan and is rarely used.
Assignment adds a new entry or replaces an existing one:
capitals['France'] = 'Paris'; // add
capitals['Norway'] = 'Bergen'; // replace
The remove method removes an entry and returns the value, or null if the key was absent:
var removed = capitals.remove('France'); // Paris
The clear method removes all entries. The length property returns the number of entries.
c. putIfAbsent and update
The putIfAbsent method adds an entry only if the key is absent. The value is computed by a function that is called only when needed.
var cache = <String, List<int>>{};
cache.putIfAbsent('a', () => []).add(1);
cache.putIfAbsent('a', () => []).add(2);
print(cache); // {a: [1, 2]}
The function is called the first time, when the key is absent. The second call finds the key present and returns the existing list. This is the pattern for lazy initialization: the value is created on first access.
The update method modifies an existing value with a function:
var scores = {'Alice': 95};
scores.update('Alice', (v) => v + 5); // 100
The function receives the current value and returns the new value. If the key is absent, update throws unless the ifAbsent parameter is provided:
scores.update('Bob', (v) => v + 5, ifAbsent: () => 0); // 0
The ifAbsent function is called when the key is absent, and its result is the new value. The updateAll method applies a function to every value:
scores.updateAll((key, value) => value + 10);
The updateAll is useful for bulk transformations.
d. Iterating keys, values, and entries
The map exposes three views: keys, values, and entries.
var scores = {'Alice': 95, 'Bob': 87, 'Carol': 92};
for (var key in scores.keys) {
print(key); // Alice, Bob, Carol
}
for (var value in scores.values) {
print(value); // 95, 87, 92
}
for (var entry in scores.entries) {
print('${entry.key}: ${entry.value}');
}
The keys view is an Iterable<K>, the values view is an Iterable<V>, and the entries view is an Iterable<MapEntry<K, V>>. Each is iterable with for-in and supports the iterable methods.
A MapEntry has a key and a value property. It is a read-only pair, used for iteration and for constructing maps from entries.
for (var entry in scores.entries) {
print('${entry.key} scored ${entry.value}');
}
The entries view is the most common for iteration when both the key and the value are needed.
e. Transforming maps
The map supports the same functional methods as the other collections, with one difference: the transformation methods operate on entries.
map transforms each entry into a new entry or a new value:
var doubled = scores.map((key, value) => MapEntry(key, value * 2));
// {Alice: 190, Bob: 174, Carol: 184}
The callback receives the key and the value and returns a MapEntry. The result is a new map.
where filters the entries:
var highScorers = scores.entries.where((e) => e.value > 90).toList();
The where method is on the entries view, not on the map itself. It returns an iterable of entries, which can be converted to a list or a map.
forEach applies a function to each entry:
scores.forEach((key, value) {
print('$key: $value');
});
The forEach callback receives the key and the value as separate arguments, unlike the other iteration methods.
map to keys or values only:
var names = scores.keys.map((k) => k.toUpperCase()).toList();
var grades = scores.values.map((v) => v >= 90 ? 'A' : 'B').toList();
f. Common patterns
Counter. A map from item to count.
var counts = <String, int>{};
for (var word in words) {
counts.update(word, (c) => c + 1, ifAbsent: () => 1);
}
The update with ifAbsent increments the count or initializes it to 1.
Grouping. A map from key to a list of items.
var byCategory = <String, List<Product>>{};
for (var product in products) {
byCategory.putIfAbsent(product.category, () => []).add(product);
}
The putIfAbsent creates the list on first use, and add appends to it.
Lookup table. A map from ID to object.
var usersById = {for (var u in users) u.id: u};
The collection-for builds the map in one expression.
Cache. A map from key to computed value.
var cache = <String, Result>{};
var result = cache.putIfAbsent(key, () => computeExpensive(key));
The putIfAbsent returns the existing value if the key is present, or computes and stores it if absent.
Inverting. A map from value to key.
var inverted = {for (var e in scores.entries) e.value: e.key};
The collection-for inverts the mapping. If two keys share a value, the later one wins.
Complete Example Session
// ============================================
// PART 1: CREATE A MAP
// ============================================
void main() {
var capitals = {
'Norway': 'Oslo',
'Sweden': 'Stockholm',
};
var scores = <String, int>{'Alice': 95};
print(capitals); // {Norway: Oslo, Sweden: Stockholm}
print(scores); // {Alice: 95}
}
// ============================================
// PART 2: ACCESS AND ASSIGN
// ============================================
void main() {
var capitals = {'Norway': 'Oslo'};
print(capitals['Norway']); // Oslo
print(capitals['France']); // null
capitals['France'] = 'Paris';
print(capitals); // {Norway: Oslo, France: Paris}
}
// ============================================
// PART 3: CONTAINSKEY AND REMOVE
// ============================================
void main() {
var capitals = {'Norway': 'Oslo', 'France': 'Paris'};
print(capitals.containsKey('Norway')); // true
var removed = capitals.remove('France');
print(removed); // Paris
print(capitals); // {Norway: Oslo}
}
// ============================================
// PART 4: PUTIFABSENT
// ============================================
void main() {
var cache = <String, List<int>>{};
cache.putIfAbsent('a', () => []).add(1);
cache.putIfAbsent('a', () => []).add(2);
print(cache); // {a: [1, 2]}
}
// ============================================
// PART 5: UPDATE
// ============================================
void main() {
var scores = {'Alice': 95};
scores.update('Alice', (v) => v + 5);
scores.update('Bob', (v) => v + 5, ifAbsent: () => 0);
print(scores); // {Alice: 100, Bob: 0}
}
// ============================================
// PART 6: ITERATE KEYS AND VALUES
// ============================================
void main() {
var scores = {'Alice': 95, 'Bob': 87};
for (var key in scores.keys) print(key);
for (var value in scores.values) print(value);
}
// ============================================
// PART 7: ITERATE ENTRIES
// ============================================
void main() {
var scores = {'Alice': 95, 'Bob': 87};
for (var entry in scores.entries) {
print('${entry.key}: ${entry.value}');
}
}
// ============================================
// PART 8: MAP METHOD
// ============================================
void main() {
var scores = {'Alice': 95, 'Bob': 87};
var doubled = scores.map((k, v) => MapEntry(k, v * 2));
print(doubled); // {Alice: 190, Bob: 174}
}
// ============================================
// PART 9: COUNTER PATTERN
// ============================================
void main() {
var words = ['a', 'b', 'a', 'c', 'b', 'a'];
var counts = <String, int>{};
for (var word in words) {
counts.update(word, (c) => c + 1, ifAbsent: () => 1);
}
print(counts); // {a: 3, b: 2, c: 1}
}
// ============================================
// PART 10: GROUPING PATTERN
// ============================================
void main() {
var products = [
{'name': 'A', 'category': 'x'},
{'name': 'B', 'category': 'y'},
{'name': 'C', 'category': 'x'},
];
var byCategory = <String, List<String>>{};
for (var p in products) {
byCategory.putIfAbsent(p['category']!, () => []).add(p['name']!);
}
print(byCategory); // {x: [A, C], y: [B]}
}
These ten parts cover creating a map, accessing and assigning, containsKey and remove, putIfAbsent, update, iterating keys and values, iterating entries, the map method, the counter pattern, and the grouping pattern.
Quick Reference
Map Creation
| Form | Example |
|---|---|
| Literal | {'a': 1, 'b': 2} |
| Typed empty | <String, int>{} |
| From iterables | Map.fromIterables(keys, values) |
| From entries | Map.fromEntries(entries) |
| Collection-for | {for (var x in xs) x.id: x} |
Access and Modify
| Operation | Method |
|---|---|
| Get | map[key] |
| Set | map[key] = value |
| Check key | map.containsKey(key) |
| Check value | map.containsValue(value) |
| Remove | map.remove(key) |
| Clear | map.clear() |
| Length | map.length |
Conditional Operations
| Operation | Method |
|---|---|
| Add if absent | map.putIfAbsent(key, () => value) |
| Update existing | map.update(key, (v) => newV) |
| Update with default | map.update(key, (v) => newV, ifAbsent: () => v) |
| Update all | map.updateAll((k, v) => newV) |
Iteration
| View | Type | Use |
|---|---|---|
keys | Iterable<K> | Iterate keys |
values | Iterable<V> | Iterate values |
entries | Iterable<MapEntry> | Iterate both |
Transformation
| Method | Purpose |
|---|---|
map((k, v) => MapEntry(...)) | Transform entries |
entries.where(...) | Filter entries |
forEach((k, v) { }) | Apply to each |
keys.map(...) | Transform keys |
values.map(...) | Transform values |
The Three Implementations
| Implementation | Ordering | Import |
|---|---|---|
LinkedHashMap | Insertion | Default |
HashMap | Unordered | dart:collection |
SplayTreeMap | Sorted | dart:collection |
Best Practices
✅ Do This:
// Use putIfAbsent for lazy initialization
cache.putIfAbsent(key, () => compute());
// Use update with ifAbsent for counters
counts.update(word, (c) => c + 1, ifAbsent: () => 1);
// Iterate entries when both key and value are needed
for (var entry in map.entries) { }
// Use collection-for to build a map
var byId = {for (var u in users) u.id: u};
// Use containsKey before accessing
if (map.containsKey(key)) { }
❌ Don’t Do This:
// Assume the key exists
var value = map[key]!; // ❌ throws if absent
// Use a list for lookups
for (var item in list) { if (item.id == id) { } } // ❌ use a map
// Forget the type on an empty map
var empty = {}; // ❌ Map<dynamic, dynamic>
// Mutate a map during iteration
for (var key in map.keys) { map.remove(key); } // ❌ concurrent modification
// Use containsValue for frequent checks
map.containsValue(x); // ❌ linear scan
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Null returned from access | Key is absent | Use containsKey or provide a default |
| Concurrent modification | Removed during iteration | Collect keys first, then remove |
{} is not an empty map | It is an empty map but untyped | Use <String, int>{} |
| Duplicate keys in literal | Later entry wins | Verify the keys are unique |
update throws | Key absent and no ifAbsent | Provide ifAbsent |
| Order not guaranteed | Used HashMap | Use the default LinkedHashMap |
Real-World Examples
1. Basic Map
var capitals = {'Norway': 'Oslo', 'Sweden': 'Stockholm'};
2. Lookup with Default
var name = namesById[id] ?? 'Unknown';
3. Counter
counts.update(word, (c) => c + 1, ifAbsent: () => 1);
4. Grouping
byCategory.putIfAbsent(category, () => []).add(item);
5. Iterate Entries
for (var entry in map.entries) { print('${entry.key}: ${entry.value}'); }
6. Transform Values
var doubled = map.map((k, v) => MapEntry(k, v * 2));
7. Filter Entries
var high = map.entries.where((e) => e.value > 90).toList();
8. Build from List
var byId = {for (var u in users) u.id: u};
9. Invert
var inverted = {for (var e in map.entries) e.value: e.key};
10. Cache
var result = cache.putIfAbsent(key, () => compute(key));
Visual
Map Structure
┌──────────────────────────────────────────────────────────────┐
│ {'Norway': 'Oslo', 'Sweden': 'Stockholm'} │
│ │
│ ┌──────────┬─────────────┐ │
│ │ key │ value │ │
│ ├──────────┼─────────────┤ │
│ │ Norway │ Oslo │ │
│ │ Sweden │ Stockholm │ │
│ └──────────┴─────────────┘ │
│ │
│ Keys are unique. Values may repeat. │
└──────────────────────────────────────────────────────────────┘
Access and Default
┌──────────────────────────────────────────────────────────────┐
│ map['Norway'] → 'Oslo' │
│ map['France'] → null │
│ map['France'] ?? 'Unknown' → 'Unknown' │
│ │
│ The bracket syntax returns null for absent keys. │
└──────────────────────────────────────────────────────────────┘
putIfAbsent Flow
┌──────────────────────────────────────────────────────────────┐
│ cache.putIfAbsent('a', () => []) │
│ │ │
│ ▼ │
│ Key 'a' present? │
│ │ │
│ ├── Yes ──▶ return existing value │
│ │ │
│ └── No ──▶ call the function, store, return │
└──────────────────────────────────────────────────────────────┘
Iteration Views
┌──────────────────────────────────────────────────────────────┐
│ map.keys → Iterable<K> │
│ map.values → Iterable<V> │
│ map.entries → Iterable<MapEntry<K, V>> │
│ │
│ Each entry has .key and .value. │
│ │
│ for (var entry in map.entries) { │
│ print('${entry.key}: ${entry.value}'); │
│ } │
└──────────────────────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
| Map | Collection of unique keys mapped to values |
| Default implementation | LinkedHashMap |
| Ordering | Insertion order (default) |
| Access | map[key] returns value or null |
| Assignment | map[key] = value |
| Check key | map.containsKey(key) |
| Lazy init | map.putIfAbsent(key, () => value) |
| Update | map.update(key, (v) => newV, ifAbsent: () => v) |
| Iterate keys | map.keys |
| Iterate values | map.values |
| Iterate entries | map.entries |
| Transform | map.map((k, v) => MapEntry(...)) |
| Filter | map.entries.where(...) |
Key takeaways:
- A
Mapis a collection of unique keys mapped to values. The key identifies the entry, and each key maps to exactly one value. Adding a value with an existing key replaces the previous value. - The default
Mapis aLinkedHashMap. It preserves insertion order, which makes iteration predictable. TheHashMapis unordered; theSplayTreeMapsorts its keys. - Access returns
nullfor absent keys. The bracket syntaxmap[key]returns a nullable value. UsecontainsKeyto check, or??to provide a default. putIfAbsentis the lazy initialization pattern. The function is called only when the key is absent. This is the tool for caches, grouping, and building maps incrementally.updatemodifies an existing value. It receives the current value and returns the new one. TheifAbsentparameter provides the value when the key is absent, which is the counter pattern.- Iterate with
keys,values, orentries. Theentriesview is the most common when both the key and the value are needed. EachMapEntryhas akeyand avalueproperty. - The transformation methods operate on entries.
mapreturns a new map,whereon the entries view filters, andforEachapplies a function to each entry.
Remember: The Map is the collection for lookups. It maps unique keys to values and provides constant-time access on average. The default LinkedHashMap preserves insertion order; the HashMap trades ordering for speed; the SplayTreeMap sorts. The three views — keys, values, and entries — cover the iteration needs. The putIfAbsent and update methods are the tools for lazy initialization and conditional updates, and they appear in almost every real-world map use: counters, grouping, caches, and lookup tables.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!