Study guide
6.2 Lists, Sets & Maps
Salesforce Certified Platform Developer I Exam
Track your progress as you read
Finished sections get a check mark.
1 Introduction & Exam Overview
1.1 Current Exam Facts 1.2 Blueprint Weights & Study Strategy
2 Platform Fundamentals
2.1 Multi-Tenant Architecture & Shared Resources 2.2 MVC Mapping on the Lightning Platform 2.3 Declarative vs Programmatic Decisions 2.4 Agentforce for Developers: Use Cases & Limitations
3 Data Modeling & Schema
3.1 Objects, Fields & Schema Design 3.2 Relationship Types & Implications 3.3 Schema Design Impact on Apex 3.4 Data Import & Export for Development
4 Platform Data Features
4.1 Formula Fields & Roll-Up Summaries 4.2 Core CRM Objects & Standard Schema 4.3 AppExchange Extensions & Platform Boundaries
5 Declarative Process Automation
5.1 Record-Triggered Flows & Declarative Automation 5.2 Approval Processes & Business Rules 5.3 When to Use Apex vs Declarative Tools
6 Apex Language Basics
6.1 Variables, Constants & Apex Data Types 6.2 Lists, Sets & Maps 6.3 Control Flow & Exception Handling 6.4 Classes, Interfaces & OOP Patterns
7 SOQL, SOSL & DML
7.1 SOQL Query Fundamentals 7.2 SOSL Search Fundamentals 7.3 DML Operations & Bulkification 7.4 Governor Limits & Transaction Boundaries
8 Apex Triggers & Order of Execution
8.1 Trigger Structure & Context Variables 8.2 Bulk Trigger Patterns & Best Practices 8.3 Save Order of Execution 8.4 Recursion, Cascading & Transaction Control
9 Asynchronous Apex
9.1 @future Methods 9.2 Queueable Apex 9.3 Batch Apex 9.4 Schedulable Apex & Job Orchestration
10 Security, Events & Schema Access
10.1 Sharing, FLS, CRUD & Injection Prevention 10.2 Platform Events Publish & Subscribe 10.3 Programmatic Schema Access (Describe)
11 Visualforce
11.1 Visualforce Pages & Markup 11.2 Standard, Custom Controllers & Extensions 11.3 Visualforce in Lightning Experience
12 Lightning Components
12.1 Lightning Component Framework Benefits 12.2 Aura Component Structure & Resources 12.3 Lightning Web Components Fundamentals 12.4 LWC Data Access: Wire, Imperative & Events
13 UI Composition & Apex Integration Patterns
13.1 Composing UI in Lightning Apps 13.2 UI Security, Performance & Accessibility Patterns 13.3 Apex Actions for Flow, LWC, and Agentforce
14 Apex Testing
14.1 Testing Framework & Coverage Requirements 14.2 Writing Unit Tests for Triggers & Classes 14.3 Test Data Strategies & Isolation 14.4 Executing Tests & Execute Anonymous Differences
15 Debugging & Developer Tools
15.1 Debug Logs & Monitoring 15.2 Developer Console, CLI, VS Code & Workbench 15.3 Sandboxes, Scratch Orgs & Environment Strategy
16 Deployment & Release
16.1 Change Sets & Metadata Deployment 16.2 Unlocked Packages, Metadata API & Salesforce DX 16.3 Release Readiness & Credential Maintenance
6.2 Lists, Sets & Maps
Key Takeaways Explain This Topic AI
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-pattern Bulk-safe pattern
- SOQL inside for (Contact c : Trigger.new) One SOQL with WHERE Id IN :ids , then Map lookup
- DML inside per-record loop Build a List/Map of changes, one DML after the loop
- Re-querying the same parent repeatedly accountsById.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
- Need Prefer
- Query results / DML input order List
- Unique IDs / membership tests Set
- Lookup related record by Id in a loop Map
- Group children by parent Map<Id, List<T>>
- Both uniqueness and later DML list Map 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
Traditional index loop — for (Integer i = 0; i < list.size(); i++) when you need the index.
List iteration — for (Account a : accounts) most readable for sObjects.
SOQL-for loop — for (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).
Map key/value iteration — keySet() / 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 .