6.2 Lists, Sets & Maps

Key Takeaways

  • List is ordered and allows duplicates—use for query results and sequence-sensitive work
  • Set stores unique elements—use to deduplicate IDs and test membership efficiently
  • Map holds key→value pairs; Map<Id, sObject> is the core bulkification pattern after a single query
  • Nested collections (Map of Lists, List of Maps) are valid and common for grouping related records
  • Prefer one query plus Maps over SOQL inside loops; Sets prevent duplicate DML targets
Last updated: August 2026

6.2 Lists, Sets & Maps

Quick Answer: Apex collections are List (ordered, duplicates allowed), Set (unique elements), and Map (key→value). Platform Developer I heavily tests Map<Id, sObject> for bulkification: query once, index by Id, then process Trigger.new without SOQL or DML inside per-record loops. Use Sets to collect unique parent IDs and block duplicate DML.

Collections sit at the center of bulk-safe Apex. If you only memorize syntax, you will miss scenario questions; if you internalize when each structure wins, trigger and governor items become straightforward.

List: Ordered Sequences

A List is an ordered collection that allows duplicate values. SOQL returns List<sObject> by default.

List<Account> accounts = [SELECT Id, Name FROM Account LIMIT 100];
List<String> statuses = new List<String>{ 'New', 'Working', 'Closed' };
statuses.add('New'); // duplicates allowed
String first = statuses[0];

Common List operations: add, addAll, get / index [], size, isEmpty, clear, contains, remove, sort (when elements are comparable).

When to use List

  • Preserving query order or insertion order
  • Passing records to DML (insert accounts)
  • Iterating every element including duplicates
  • Building result sequences for return values and batch chunks

Exam tip: List index is zero-based. Accessing an invalid index throws a runtime exception—check size() or use safe iteration.

Set: Uniqueness and Membership

A Set stores unique elements. Adding a duplicate is ignored (no error, no second copy).

Set<Id> accountIds = new Set<Id>();
for (Contact c : Trigger.new) {
    if (c.AccountId != null) {
        accountIds.add(c.AccountId);
    }
}
// accountIds has each parent Id at most once
Boolean hasId = accountIds.contains(someId);

When to use Set

  • Collecting related record IDs from Trigger.new without duplicates
  • Fast membership tests (contains) before expensive work
  • Deduplicating strings or enums before processing
  • Building SOQL bind collections: WHERE Id IN :accountIds

Sets do not guarantee order. Do not rely on iteration order for business sequencing—use a List if order matters.

Duplicate prevention pattern: Before DML, put candidates in a Map<Id, sObject> or track Ids in a Set so the same record is not updated twice in one transaction (duplicate Id in one update list causes errors).

Map: Keys to Values (Bulkification Hero)

A Map associates keys with values. The most important exam pattern is Map<Id, sObject> after a single query:

Set<Id> accountIds = new Set<Id>();
for (Contact c : Trigger.new) {
    if (c.AccountId != null) accountIds.add(c.AccountId);
}

Map<Id, Account> accountsById = new Map<Id, Account>([
    SELECT Id, Name, Industry
    FROM Account
    WHERE Id IN :accountIds
]);

for (Contact c : Trigger.new) {
    Account parent = accountsById.get(c.AccountId);
    if (parent != null && parent.Industry == 'Agriculture') {
        // use parent fields without extra SOQL
    }
}

The constructor new Map<Id, sObject>(listOfSObjects) indexes each record by its Id automatically—an idiomatic shortcut tested often.

Why Maps bulkify code

Anti-patternBulk-safe pattern
SOQL inside for (Contact c : Trigger.new)One SOQL with WHERE Id IN :ids, then Map lookup
DML inside per-record loopBuild a List/Map of changes, one DML after the loop
Re-querying the same parent repeatedlyaccountsById.get(c.AccountId)

map.get(key) returns null when the key is missing—always null-check before using the value, or use containsKey first.

Other useful Map APIs: put, putAll, keySet(), values(), size(), isEmpty(), remove.

for (Id accId : accountsById.keySet()) {
    Account a = accountsById.get(accId);
}
for (Account a : accountsById.values()) {
    // iterate records directly
}

Nested Collections

You can nest collections for grouping:

Map<Id, List<Contact>> contactsByAccount = new Map<Id, List<Contact>>();
for (Contact c : [
    SELECT Id, AccountId, Email FROM Contact WHERE AccountId IN :accountIds
]) {
    if (!contactsByAccount.containsKey(c.AccountId)) {
        contactsByAccount.put(c.AccountId, new List<Contact>());
    }
    contactsByAccount.get(c.AccountId).add(c);
}

Common shapes:

  • Map<Id, List<sObject>> — children grouped by parent Id
  • Map<String, Set<Id>> — keys like external keys or statuses to Id sets
  • List<Map<String, Object>> — less common; sometimes used for generic payloads

Nested collections increase heap usage—still better than exploding SOQL rows inside loops, but avoid building huge graphs when a single aggregate query would suffice.

Choosing List vs Set vs Map

NeedPrefer
Query results / DML input orderList
Unique IDs / membership testsSet
Lookup related record by Id in a loopMap
Group children by parentMap<Id, List<T>>
Both uniqueness and later DML listMap values → List, or Set of Ids then requery

Id uniqueness: If you only need unique Ids for a bind variable, Set is enough. If you need the full parent record fields in a loop, Map<Id, sObject> is required.

Iteration Patterns That Show Up on the Exam

  1. Traditional index loopfor (Integer i = 0; i < list.size(); i++) when you need the index.
  2. List iterationfor (Account a : accounts) most readable for sObjects.
  3. SOQL-for loopfor (Account a : [SELECT ...]) streams rows in chunks and is governor-friendlier for large result sets than loading one giant List (covered more with SOQL chapters).
  4. Map key/value iterationkeySet() / values() as shown above.

Avoid modifying a List’s structure while iterating it with a for-each loop in ways that confuse indices; build a separate “to delete” or “to update” collection instead.

Duplicate Prevention with Sets (and Maps)

Scenarios:

  • Multiple Contacts in Trigger.new share one AccountId → Set collapses to one query key
  • Building List<Account> toUpdate → use Map<Id, Account> toUpdate and put by Id so each Account appears once
  • Same record qualified by two rules → Map last-write or merge fields deliberately
Map<Id, Account> toUpdate = new Map<Id, Account>();
// ... business rules ...
toUpdate.put(a.Id, a); // last put wins for that Id
update toUpdate.values();

Exam Mindset

When a question shows SOQL inside a loop over Trigger.new, the fix is almost always: collect Ids in a Set, query into a Map, loop again with get. When a question asks which collection removes duplicates, answer Set (or Map keys). When order of insertion matters for processing, answer List.

Test Your Knowledge

A trigger must read parent Account fields for every Contact in Trigger.new without querying inside the loop. Which collection pattern is most appropriate?

A
B
C
D
Test Your Knowledge

Why is a Set often used when gathering AccountId values from Trigger.new before a SOQL query?

A
B
C
D
Test Your Knowledge

What does map.get(someId) return when someId is not present as a key?

A
B
C
D