Collections: List, Set, and Map
The three collection interfaces you'll use in nearly every Java program, and the practical rule of thumb for choosing between them.
What you'll learn
- Choose List, Set, or Map based on a data shape's actual requirements
- Use core methods of ArrayList, HashSet, and HashMap correctly
- Explain why List/Set/Map are interfaces, and name a common implementation of each
Prerequisites
Explanation
The Java Collections Framework centers on three interfaces that cover almost every data-shape need: List (an ordered, index-accessible, duplicate-allowing sequence — think "resizable array"), Set (an unordered collection that automatically rejects duplicates), and Map (a key-to-value lookup, where each key appears at most once). List<String> names = new ArrayList<>(); is the workhorse List implementation, growing automatically as you .add() elements — internally, exactly by allocating a new backing array and copying, as the arrays lesson foreshadowed. Set<String> unique = new HashSet<>(); guarantees no duplicate ever gets stored (adding an element already present is a no-op that returns false), and offers O(1) average-time .contains() checks, which is the whole reason to reach for a Set instead of scanning a List by hand. Map<String, Integer> ages = new HashMap<>(); associates each unique key with one value — .put(key, value) inserts or overwrites, .get(key) returns the value or null if the key is absent, and .getOrDefault(key, fallback) avoids a separate null-check when you want a default.
These are declared as interfaces on the left, concrete implementations on the right (List<String> names = new ArrayList<>();, not ArrayList<String> names = new ArrayList<>();) as a deliberate, idiomatic convention: code written against the interface type can swap implementations (ArrayList for LinkedList, HashMap for LinkedHashMap) without changing a single line beyond that one declaration, because every caller only ever relies on the interface's guarantees, never on implementation-specific behavior.
The practical decision rule: reach for a List when order and/or duplicates matter and you need index access; reach for a Set when you only care "is this present," never "how many times" or "in what order"; reach for a Map the moment you're looking something up by a key rather than scanning for it — if you ever catch yourself writing a loop that scans a List purely to check whether some field matches, that's almost always a sign a Map (keyed by that field) or a Set would be both clearer and faster.
Example
The three collection shapes, modeled with JS's own Array/Set/Map -- the API and the choose-by-shape reasoning carry over directly.
const names = []; // List-like: ordered, duplicates allowed, index access
names.push("Alex");
names.push("Alex"); // duplicates allowed
console.log(names, names.length); // ["Alex", "Alex"] 2
const uniqueNames = new Set(); // Set-like: no duplicates, no index access
uniqueNames.add("Alex");
uniqueNames.add("Alex"); // no-op, already present
console.log(uniqueNames.size); // 1
const ages = new Map(); // Map-like: keyed lookup
ages.set("Alex", 30);
ages.set("Alex", 31); // overwrites the previous value for this key
console.log(ages.get("Alex")); // 31
console.log(ages.get("Sam")); // undefined -- Java's Map.get returns null here insteadTry it yourself
Add a second, different name to uniqueNames and print its size.
Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.
Guided exercise
Guided exercise
Write countUnique(items) modeling Java's Set behavior: return the number of DISTINCT values in items (use a Set to dedupe, then read its size).
Checks: counts distinct values correctly · handles an empty array · collapses an all-duplicate array to 1
Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.
Stuck? Get a hint.
Independent exercise
Independent exercise
Write wordFrequency(words) modeling Java's Map<String, Integer> pattern: return an object (standing in for a Map) mapping each word to how many times it appears in the words array.
Checks: counts word frequency correctly across repeats · handles an empty input array
Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.
Stuck? Get a hint.
Common mistakes
- Using a List and scanning it linearly to check membership when a Set or Map would be both clearer and far faster for large data.
- Calling map.get(missingKey) and using the result without a null-check -- it returns null, not a default or an exception, and using null unguarded throws a NullPointerException on the next operation.
- Declaring `ArrayList<String> names = new ArrayList<>();` instead of `List<String> names = new ArrayList<>();` -- it compiles fine, but it loses the ability to swap implementations later without touching every line that uses the variable's declared type.
Knowledge check
Takeaway
Choose List for ordered data with duplicates and index access, Set for fast 'is this present' checks with no duplicates, and Map the moment you're looking something up by a key — and always declare the variable's type as the interface, not the concrete implementation.
Summary
List, Set, and Map are the three core collection interfaces. ArrayList, HashSet, and HashMap are their most common implementations. Map.get returns null for a missing key; getOrDefault avoids the null-check. Programming to the interface keeps implementations swappable.
References
Your notes
Notes save automatically.
Finished this lesson?
Mark it complete to track your progress and schedule a future review.
AI tutor
The optional AI tutor isn't enabled in this deployment. All lessons, exercises, quizzes, and search work fully without it.