A collection in Apex is a container that holds multiple values of the same declared type. Apex gives you exactly three collection types — List, Set, and Map — and every bulkification pattern you’ll ever write leans on at least one of them.
The three collection types
| Type | Ordered? | Allows duplicates? | Indexed by | Typical use |
|---|---|---|---|---|
List<T> | Yes (insertion order) | Yes | Integer position | Trigger.new, query results, ordered processing |
Set<T> | No | No | (no index) | Deduplicated IDs, “have I seen this?” checks |
Map<K, V> | No | Keys are unique | Key of type K | Lookups by Id, parent-to-child grouping |
List
A List<T> is an ordered, resizable array. SOQL returns List<SObject> by default, and Trigger.new is a List<SObject>. Position-based access uses get(index) or [index].
List<Account> accounts = [SELECT Id, Name FROM Account LIMIT 10];
accounts.add(new Account(Name = 'Acme'));
Account first = accounts[0]; // index access
Integer howMany = accounts.size();
Set
A Set<T> enforces uniqueness — adding a value that’s already in the set is a no-op. Order is not guaranteed. The classic use is collecting parent IDs to query in bulk:
Set<Id> accountIds = new Set<Id>();
for (Case c : Trigger.new) {
if (c.AccountId != null) {
accountIds.add(c.AccountId); // duplicates auto-collapsed
}
}
// One query covers every distinct parent
Map<Id, Account> accountsById = new Map<Id, Account>([
SELECT Id, Name FROM Account WHERE Id IN :accountIds
]);
Map
A Map<K, V> stores key-value pairs with unique keys. The most common Apex pattern is Map<Id, SObject> — you can construct one directly from a SOQL result and get O(1) lookups by record Id.
Map<Id, Account> accountsById = new Map<Id, Account>([
SELECT Id, Name, Industry FROM Account WHERE Industry = 'Tech'
]);
Account a = accountsById.get(someId); // null if not present
Boolean exists = accountsById.containsKey(someId);
Why bulk patterns depend on collections
Without collections, every record in a trigger context would force its own SOQL or DML — that hits governor limits the first time a real workload arrives. The bulkification recipe is always the same:
- Loop once through
Trigger.newto collect IDs into aSetorMap. - Query once outside the loop, into a
Map<Id, SObject>for O(1) lookups. - Loop again to mutate records, accumulating updates into a
List<SObject>. - DML once on the list.
That four-step pattern is the difference between a trigger that survives a data loader import and one that fails on the first batch of 200.
Type safety and nullability
Apex collections are generic and strongly typed. List<Account> cannot accept an Opportunity. The compiler enforces this. Methods like map.get(key) return null when the key isn’t present — there is no Optional/Maybe, so guard with containsKey() or null checks when missing keys are meaningful.
What interviewers are really looking for
The textbook answer is “List, Set, Map.” The interview-worthy answer is why each one exists in the bulkification pattern. If you can show — in code or words — how Set<Id> deduplicates, how Map<Id, SObject> replaces inner-loop SOQL, and how List<SObject> accumulates DML, you’ve signaled production-readiness.
Verified against: Apex Developer Guide — Collections. Last reviewed 2026-05-17 for Spring ‘26 release.