Learn · OOP & Java
Java Collections
Java hands you ready-made containers so you never rebuild a list or a lookup table by hand. Knowing which to pick is half of writing clean code — and a constant interview question.
Before we start
- What a collection is and the four you must know
- List vs Set vs Map vs Queue — and when to reach for each
- Why a Set can’t hold duplicates (the trick underneath)
- How these build directly on Arrays & Hashing
- Pick the right collection for a problem, on purpose
- Explain why a Set removes duplicates automatically
- Use List, Set, Map and Queue in real Java code
Why you’re learning it: collections appear in every Java program and every interview (“List vs Set?”). They’re the practical face of your Arrays & Hashing lesson. ⏱️ ~25 min.
The idea — the right container for the job
You wouldn’t carry soup in a basket or books in a bucket. Data is the same: each collection is a container shaped for a job. Pick by asking two questions — do I need order? and do I find things by position, by uniqueness, or by a key? Here are the four, drawn out:
📋 List — an ordered, numbered shelf
Keeps order, allows duplicates, reach any item by its index. (Like the array you already know — but it grows.)
Note the two As — a List is happy with duplicates.
🚫 Set — a guest list (each name once)
Add the same thing twice and the second is silently ignored. No order, but instant “is this in here?”.
🗺️ Map — labelled drawers (key → value)
Store a value under a key, fetch it instantly by that key. A dictionary; your phone contacts.
🎟️ Queue — a line at the counter (FIFO)
First in, first served. Add at the back, remove from the front.
Choose the right one
| Need | Use | (Java class) | Reach for it when… |
|---|---|---|---|
| List | List | ArrayList | a sequence — a playlist, rows from a query |
| Set | Set | HashSet | no duplicates — unique visitors, “seen before?” |
| Map | Map | HashMap | look up by name/id — user by email |
| Queue | Queue | LinkedList / ArrayDeque | process in order — job/task queues |
The key insight
A Set has no duplicates because underneath it’s a Map using the items themselves as keys — and keys are unique by definition. A HashMap gives O(1) lookups for the exact reason from your hashing lesson (the hash points straight to the slot). So collections aren’t new magic — they’re the data structures you already learned, gift-wrapped.
Why not just use arrays?
You already know arrays. Here’s exactly where they hurt — and why collections exist:
Need a 6th slot in a size-5 array? Make a bigger one and copy everything by hand.
Deleting index 2 means manually shifting everything after it down.
You write the search loop yourself, every single time.
“What’s Ravi’s balance?” forces a scan of the whole array.
Collections are classes that do all of this for you — growing, shifting, searching — battle-tested for 25 years.
The family tree
Collections are interfaces (the contract) with multiple implementations (the engine). You declare the interface, instantiate the implementation:
List<String> names = new ArrayList<>(); // contract on the left, engine on the right
Why? Code that depends on List doesn’t care which engine is underneath — swap engines later without rewriting callers.
Note Map is drawn apart — technically it’s not a Collection — but everyone discusses them together because you use them together.
See them in real Java
📋 ArrayList — the workhorse (an array that resizes itself)
List<Integer> expenses = new ArrayList<>(); expenses.add(250); // chai and samosa run expenses.add(1200); // electricity bill expenses.add(250); // duplicates are fine in a List expenses.remove(Integer.valueOf(1200)); // remove by value, not index expenses.get(0); // indexed access: 250 expenses.size(); // 2 expenses.contains(250); // true
How it grows (interview favorite): inside is a plain array (default capacity 10). Add the 11th element and the ArrayList makes a new array ~1.5× bigger and copies everything across. That copy is occasionally costly, but rare enough that adding stays cheap on average. get(i) is instant — pure array math, jump straight to the slot.
The <Integer> in angle brackets is generics — it tells the compiler “this list holds Integers only,” so it blocks a String at compile time.
🔗 LinkedList — exists, rarely wins
Stores elements as a chain of nodes, each pointing to the next. Textbooks praise “fast insertion in the middle.” Honest reality:
get(i)means walking the chain from the start — slow.- Each node is a separate object scattered across memory, which modern CPUs hate; they’re fast when data sits side by side — exactly what ArrayList’s internal array gives.
- In real benchmarks
ArrayListwins almost every realistic workload, including many “insert in the middle” cases.
Know it exists, say why it loses, default to ArrayList. Senior answer: “I’d reach for LinkedList only with a measured reason — and I’ve never had one.”
🚫 HashSet — no duplicates, fast contains
Set<String> handles = new HashSet<>();
handles.add("darshan@upi"); // true — added
handles.add("ravi@upi"); // true — added
handles.add("darshan@upi"); // false — already there, ignored
handles.size(); // 2
handles.contains("ravi@upi"); // true — and FASTTwo things to bank: (1) add returns false if the element was already present — a free “have I seen this before?” check. (2) contains is fast regardless of size — 10 elements or 10 million cost roughly the same, while a List’s contains scans every element. Warning: a HashSet has no order — iterate it and items come out however hashing decided.
🗺️ HashMap — key → value lookup
Map<String, Integer> balances = new HashMap<>();
balances.put("darshan@upi", 5000);
balances.put("ravi@upi", 12000);
balances.put("darshan@upi", 4500); // same key — value REPLACED, not duplicated
balances.get("darshan@upi"); // 4500
balances.getOrDefault("priya@upi", 0); // 0 — safe default for missing keys
balances.containsKey("ravi@upi"); // trueHashMap internals — THE interview classic
The question interviewers actually ask: how does get find the value without scanning everything? Inside, a HashMap is an array of buckets. The journey of balances.get("ravi@upi"):
- Call
hashCode()on the key — gives a number, say 83214041. - Squash that number into a bucket index — roughly hash mod array length — say bucket 6.
- Jump straight to bucket 6. No scanning, no searching.
- Two keys can land in the same bucket (a collision), so a bucket holds a short chain of entries. Walk that chain, using
equals()to find the exact key. - Return that entry’s value.
Why equals and hashCode must agree: equal objects must have equal hash codes. Break it, and your key hashes to bucket 6 on put but you search bucket 3 on get — the entry is there, and you can never find it. The map looks like it’s losing data. It isn’t — your hashCode is lying. Say “hash to bucket, then equals within the bucket” in an interview and you’re ahead of most freshers.
A first taste of Big-O
Big-O answers one thing: when the data gets 1000× bigger, does the operation get 1000× slower — or stay the same? Two shapes for now: O(1) constant (same speed at any size — jumping to a slot) and O(n) linear (doubles when data doubles — scanning everything).
| Operation | Cost | Why |
|---|---|---|
list.get(index) | O(1) | Array math — jump straight to the slot |
list.contains(x) | O(n) | Must check every element until found |
set.contains(x) | O(1) avg | Hash to bucket, check a tiny chain |
map.get(key) | O(1) avg | Same hash-to-bucket jump |
map.put(key, val) | O(1) avg | Same again |
The practical takeaway: call list.contains inside a loop over another big list and you’ve built an O(n²) bomb — fine with 50 rows in testing, 30-second page loads with 50,000 in production. Swapping that inner list for a HashSet is one of the most common real-world performance fixes there is.
The iteration trap every dev hits once
You cannot structurally change a collection while a for-each is walking it:
for (Integer amount : amounts) {
if (amount < 200) {
amounts.remove(amount); // BOOM — ConcurrentModificationException
}
}The iterator notices the collection shifted under its feet and throws — on purpose, because continuing would give garbage. The modern fix is one line:
amounts.removeIf(amount -> amount < 200); // safe, and clearer about intent
That arrow is a lambda — read it as “for each amount, is it under 200?” Full lambda treatment comes later.
Two traps you’ll hit in week one
List.of(...) returns a read-only list. Call add/remove on it and you get UnsupportedOperationException. Wrap it: new ArrayList<>(List.of(...)) — copy first, then modify.
HashSet/HashMap trade order for speed. Need sorted or insertion order? Java has other implementations that cost a little speed for it — parked for later.
How to choose — the decision tree
Three questions, in order: lookup by key? uniqueness? order? Default to ArrayList only when nothing else claims the job — not as a reflex.
Where you’ll use it — real life
Holding query rows, config by key, unique ids — daily Java.
Drop a list into a Set and duplicates vanish for free.
A Map turns “find the user with this id” into one call.
Your Relay task-queue project is a Queue in action.
Now YOU do the reps
First, the recognition drill — pick the collection before opening the answer:
You must remove duplicate user-IDs from a list. Which collection?
Set — add them all; duplicates drop automatically.
You need to look up an order by its order-number, fast. Which?
Map — key = order number, value = the order.
You’re processing sign-ups strictly in the order they arrived. Which?
Queue — FIFO, first come first served.
You need the top 10 songs in ranked order, duplicates possible. Which?
List — ordered and duplicates allowed; index = rank.
Then code them (both use collections directly):
Interview self-check
Answer each out loud before opening it — these are the exact questions Indian Java interviews ask:
Name three things collections give you that plain arrays don’t.
Automatic resizing, easy removal, built-in contains, key-value lookup (Map), uniqueness enforcement (Set) — any three.
What happens inside an ArrayList when you add beyond its capacity?
It allocates a new internal array ~1.5× larger, copies all existing elements into it, then adds the new one. Rare enough that adds stay cheap on average.
Why does ArrayList beat LinkedList in most real workloads?
ArrayList’s elements sit side by side in one array, which CPUs read fast, and get is direct array math. LinkedList scatters nodes across memory and must walk the chain for access.
Walk through the full journey of map.get("ravi@upi").
Call hashCode() on the key, squash it into a bucket index, jump straight to that bucket, walk the short chain there using equals() to match the exact key, return the value (or null).
What’s a hash collision, and how does HashMap deal with it?
Two different keys hashing to the same bucket. The bucket holds a small chain of entries, and equals picks the right one within it.
Why is set.contains O(1) but list.contains O(n) — and when does it hurt?
The set hashes straight to a bucket; the list checks elements one by one. It hurts when contains runs inside a loop over big data — O(n) inside O(n) is O(n²), the classic “fast in dev, dead in production” pattern.
What exception do you get removing from a list in a for-each, and the clean fix?
ConcurrentModificationException — the iterator detects the collection changed mid-walk. Fix: list.removeIf(condition).
Store unique visitor IDs, check membership millions of times — which collection?
HashSet — uniqueness is enforced automatically and contains stays O(1) no matter how many millions of IDs are in it.
Build this yourself
Two small terminal tools, both solving problems collections were born for:
- WordFrequency.java — read a line with
Scanner, lowercase,split(" "), then build aMap<String, Integer>where each word maps to its count. The key move:counts.put(word, counts.getOrDefault(word, 0) + 1);. Feed it “the quick brown fox jumps over the lazy dog the end” and verifytheshows 3. - DuplicateExpenses.java — loop an array of expense strings with sneaky duplicates into a
HashSet<String>. Whenaddreturnsfalse, print that entry as a duplicate — a double-spend caught in one pass. - Break it on purpose: swap
getOrDefaultfor plaingetand watch theNullPointerExceptionon the first new word. Understand why, then put it back.
Out loud: “When do I use a List vs a Set vs a Map vs a Queue, why does a Set have no duplicates, and how does a HashMap retrieve a value (including collisions and why equals + hashCode must agree)?” Then log it in your Journal.
Why AI can’t do this for you: AI will pick a collection for you — usually ArrayList, usually without thinking. It won’t be in the room when the page that was instant in dev takes 30 seconds on production data, and the fix is knowing an O(n) contains inside a loop should have been a HashSet from day one. Choosing data structures for data you haven’t seen yet is judgment. The HashMap interview question exists precisely to separate people with that judgment from people who prompt for it.
Next: Exceptions →