In Salesforce development, writing efficient, bulkified Apex code depends entirely on how well you structure and manage data in memory. Collections are the core data structures in Apex used to process records in bulk, prevent governor limit violations, and optimize database transactions. Apex provides three primary collection types: Lists, Sets, and Maps.
1. Apex Lists: Ordered Collections with Duplicates
A List is an ordered collection of elements indexed by an integer starting at position 0. Lists allow duplicate elements and are the primary data structure returned by SOQL queries and accepted by DML operations.
// Declaration & Initialization
List<String> fruits = new List<String>(); // Empty list
List<String> colors = new List<String>{'Red', 'Green', 'Blue'}; // Initialized with values
// Adding elements
fruits.add('Apple');
fruits.addAll(colors);
// Accessing & Removing elements by index
String firstFruit = fruits.get(0); // or fruits[0]
Integer totalCount = fruits.size();
fruits.remove(0); // Removes the item at index 0
// SOQL query assigning directly to a List
List<Contact> contactList = [
SELECT Id, FirstName, LastName, Email
FROM Contact
WITH USER_MODE
LIMIT 10
];
2. Apex Sets: Unordered Collections of Unique Elements
A Set is an unordered collection of distinct elements. Sets do not allow duplicates and provide fast, constant-time membership lookups via the contains() method. Sets are essential for collecting unique Record IDs to filter SOQL queries efficiently.
// Declaration & Initialization
Set<String> cities = new Set<String>();
Set<String> uniqueColors = new Set<String>{'Red', 'Green', 'Blue'};
// Adding elements (duplicates are automatically ignored)
cities.add('New York');
cities.add('New York'); // Set still contains only one 'New York'
// Fast membership verification
Boolean hasRed = uniqueColors.contains('Red'); // Returns true
// Real-world use case: Extracting unique Email addresses from Contacts
Set<String> uniqueEmails = new Set<String>();
for (Contact con : contactList) {
if (String.isNotBlank(con.Email)) {
uniqueEmails.add(con.Email.toLowerCase());
}
}
3. Apex Maps: Key-Value Associations for Fast Lookups
A Map is a collection of key-value pairs where each key is strictly unique and points to exactly one value. Maps eliminate nested loops in Apex, allowing developers to correlate parent-child records or look up reference values in O(1) time complexity.
// Declaration & Basic Operations
Map<String, String> countryCodes = new Map<String, String>{
'US' => 'United States',
'CA' => 'Canada'
};
countryCodes.put('IN', 'India');
String country = countryCodes.get('US'); // Returns 'United States'
// Constructing a Map directly from a SOQL Query
Map<Id, Account> accountMap = new Map<Id, Account>([
SELECT Id, Name, Industry
FROM Account
WITH USER_MODE
LIMIT 100
]);
// Grouping multiple Child records (Opportunities) under a Parent Key (AccountId)
Map<Id, List<Opportunity>> accountToOppsMap = new Map<Id, List<Opportunity>>();
for (Opportunity opp : [SELECT Id, Name, Amount, AccountId FROM Opportunity WHERE AccountId != null LIMIT 500]) {
if (!accountToOppsMap.containsKey(opp.AccountId)) {
accountToOppsMap.put(opp.AccountId, new List<Opportunity>());
}
accountToOppsMap.get(opp.AccountId).add(opp);
}
- List: Ordered, allows duplicates, accessible by zero-based index. Best for DML operations and sorted UI display.
- Set: Unordered, strictly unique items. Best for collecting IDs for
IN :idsSetSOQL filters and deduplication. - Map: Key-value store, unique keys, instant lookups. Best for matching related records without nested loops.
- Heap Size Limit: All collections count against the synchronous 6 MB (or asynchronous 12 MB) Apex heap limit.
4. Bulkification Traps & Best Practices
Iterating through a list inside another list loop creates O(n²) complexity, driving up CPU time and risking the
Apex CPU time limit exceeded error. Always populate a Map first, then retrieve records with a single lookup using map.get(key).
- Direct Query Initialization: Use the built-in Map constructor
new Map<Id, sObject>([SOQL])to populate an ID-to-Record map in a single line of code. - Null Safety on Map Retrieval: Always verify that a key exists using
containsKey()before calling methods on the returned value to avoidNullPointerExceptionerrors. - Case Sensitivity Consideration: Apex Set and Map keys for strings are case-sensitive by default (e.g.,
'Apex'vs'apex'). For case-insensitive uniqueness, normalize strings usingtoLowerCase()before inserting.
Summary
Mastering Lists, Sets, and Maps is the foundation of building robust, production-ready Salesforce solutions. By leveraging Lists for DML, Sets for deduplication, and Maps for high-speed record linking, you can write clean, governor-limit-friendly Apex that scales effortlessly across large data volumes.