Made by VeraStack Labs
Pack the right bag
Every trip needs a different bag.
You are going on a trip. For a weekend away you take a rucksack. For a month abroad you take a suitcase. For the train to college you take a small bag for your laptop and lunch. Nobody wonders which one is "the best bag". The question is always "the best bag for this trip".
Collections work the same way. After ten lessons you have met a whole set of them, and none is the best. ArrayList is perfect for a shopping list and wrong for a phone book. HashMap is perfect for a phone book and wrong for an undo button. Choosing well is mostly a matter of asking what the trip is.
Good news: you only need three questions. This lesson turns them into a guide you can keep open next to your editor. It is the lesson most students come back to.
Remember: don't ask "which collection is best?" Ask "what do I need to do with these items?"
Three questions, one answer
Follow the arrows from the top.
Read the picture from the top.
- Do you look things up by a key? A key is a name or ID that points to a value, like a name in a phone book. If yes, you need a Map. Then ask about order: no order needed means
HashMap, the order you added meansLinkedHashMap, and sorted keys meansTreeMap. - If not: should each item appear only once? If yes, you need a Set, and the same order question picks
HashSet,LinkedHashSetorTreeSet. - If not: how is the list used? To read by position, such as "the third item", use
ArrayList. To work at the ends, as a stack (newest first) or a queue (oldest first), useArrayDeque. To always take the most urgent item next, usePriorityQueue.
And one more rule that holds throughout: declare the variable as the interface (List, Set, Map, Deque, Queue) and only write the class name after new. Then changing your mind later means changing one word.
Seven trips, seven bags
Each pick, in a small example.
Every block below can be pasted into jshell. Each one names the need first and then the pick.
Trip 1: a shopping list. Order matters, repeats are fine, and you read by position.
// Order matters, repeats are fine, and you read by position: an ArrayList.
List<String> bag = new ArrayList<>();
bag.add("milk");
bag.add("eggs");
bag.add("milk"); // two packets of milk
System.out.println(bag);
System.out.println(bag.get(1));Paste into jshell to try it
[milk, eggs, milk]
eggsTrip 2: who has visited? You only want to know "have I seen this one?", and each person counts once.
// "Have I seen this before?" and "no repeats": a HashSet.
Set<String> visitors = new HashSet<>();
String[] arrivals = {"asha", "ravi", "asha", "meera", "ravi", "asha"};
for (String id : arrivals) {
visitors.add(id);
}
System.out.println(visitors.size());
System.out.println(visitors.contains("meera"));Paste into jshell to try it
3
trueSix arrivals, three different people. A set absorbs the repeats on its own.
Trip 3: no repeats, but keep the order they first came in.
// No repeats, and the first-seen order kept: a LinkedHashSet.
List<String> raw = List.of("b", "a", "b", "c", "a");
Set<String> unique = new LinkedHashSet<>(raw);
System.out.println(unique);Paste into jshell to try it
[b, a, c]A LinkedHashSet is a HashSet that remembers insertion order. Copying a list into one is a neat way to remove repeats without scrambling the list.
Trip 4: a phone book. You look up by name, and now and then list everyone A to Z.
// Look up by name: a HashMap. List them A to Z: copy into a TreeMap.
Map<String, String> contacts = new HashMap<>();
contacts.put("Ravi", "98450 22222");
contacts.put("Asha", "98450 11111");
contacts.put("Meera", "98450 33333");
System.out.println(contacts.get("Meera"));
System.out.println(new TreeMap<>(contacts));Paste into jshell to try it
98450 33333
{Asha=98450 11111, Meera=98450 33333, Ravi=98450 22222}A HashMap for the quick lookups. When you need the sorted list, copy it into a TreeMap just for that moment.
Trip 5: undo. The newest change is the first to be undone.
// The newest edit is undone first: a stack, so an ArrayDeque.
Deque<String> edits = new ArrayDeque<>();
edits.push("typed 'Hel'");
edits.push("typed 'lo'");
edits.push("deleted 'o'");
System.out.println(edits.pop());
System.out.println(edits.pop());Paste into jshell to try it
deleted 'o'
typed 'lo'Trip 6: a print line. The oldest job goes first.
// The oldest job is served first: a queue, so an ArrayDeque again.
Queue<String> jobs = new ArrayDeque<>();
jobs.offer("report.pdf");
jobs.offer("photo.png");
System.out.println(jobs.poll());
System.out.println(jobs.size());Paste into jshell to try it
report.pdf
1Both of those are ArrayDeque: one used as a stack, one as a queue.
Trip 7: the next train. Whatever order the times are added in, you always want the earliest.
// Always the earliest departure first, whatever order they arrive in:
// a PriorityQueue.
PriorityQueue<Integer> departures = new PriorityQueue<>();
departures.offer(1545);
departures.offer(915);
departures.offer(1210);
System.out.println(departures.poll());
System.out.println(departures.poll());Paste into jshell to try it
915
1210Changing your mind later
Because the variable is declared as Map, the code that uses it does not care which map it got.
// Written against Map, so any map will do.
static int total(Map<String, Integer> marks) {
int sum = 0;
for (int value : marks.values()) {
sum += value;
}
return sum;
}Paste into jshell to try it
Map<String, Integer> marks = new HashMap<>(); // change this one word to TreeMap...
marks.put("Asha", 91);
marks.put("Ravi", 72);
marks.put("Meera", 65);
System.out.println(total(marks)); // ...and nothing below needs to changePaste into jshell to try it
228Change HashMap to TreeMap on the first line and the total is the same. This is the habit from lesson 1 paying off. Pick the class you need today, and keep the door open.
The guide as code
If it helps to see the guide as a program, here it is. Each branch is one arrow in the picture.
enum Need { LOOK_UP_BY_KEY, UNIQUE_ITEMS, BY_POSITION, STACK_OR_QUEUE, MOST_URGENT_FIRST }
enum Order { ANY, AS_ADDED, SORTED }
/** The decision guide, written as code. */
static String suggest(Need need, Order order) {
return switch (need) {
case LOOK_UP_BY_KEY -> switch (order) {
case ANY -> "HashMap";
case AS_ADDED -> "LinkedHashMap";
case SORTED -> "TreeMap";
};
case UNIQUE_ITEMS -> switch (order) {
case ANY -> "HashSet";
case AS_ADDED -> "LinkedHashSet";
case SORTED -> "TreeSet";
};
case BY_POSITION -> "ArrayList"; // LinkedList is almost never the better answer
case STACK_OR_QUEUE -> "ArrayDeque";
case MOST_URGENT_FIRST -> "PriorityQueue";
};
}Paste into jshell to try it
System.out.println(suggest(Need.LOOK_UP_BY_KEY, Order.ANY));
System.out.println(suggest(Need.LOOK_UP_BY_KEY, Order.SORTED));
System.out.println(suggest(Need.UNIQUE_ITEMS, Order.AS_ADDED));
System.out.println(suggest(Need.STACK_OR_QUEUE, Order.ANY));
System.out.println(suggest(Need.MOST_URGENT_FIRST, Order.ANY));Paste into jshell to try it
HashMap
TreeMap
LinkedHashSet
ArrayDeque
PriorityQueueThe table below says the same thing in words. This is the one to screenshot.
| If you need to... | Use | Because |
|---|---|---|
| Keep items in a row, read by position, repeats allowed | ArrayList | Jump to any index at once |
| Look up a value by a key | HashMap | Fastest lookup on average |
| Look up by key, and walk in the order added | LinkedHashMap | A hash map that remembers order |
| Look up by key, with sorted keys or nearest-key queries | TreeMap | A sorted tree |
| Hold each item once, and ask "is it in?" | HashSet | Fastest membership check on average |
| Hold each item once, in the order added | LinkedHashSet | A hash set that remembers order |
| Hold each item once, sorted | TreeSet | A sorted tree |
| Use newest-first (undo, brackets, depth-first search) | ArrayDeque as a stack | Both ends are quick |
| Use oldest-first (a waiting line, breadth-first search) | ArrayDeque as a queue | Both ends are quick |
| Always take the most urgent item next | PriorityQueue | A heap keeps the best on top |
| Share a list that others must not change | List.of or List.copyOf | Cannot be changed |
Notice who is missing: LinkedList. It is not in the table because there is almost always a better row. Lesson 4 explains why.
Remember: three questions. A key to look things up by? Each item only once? Otherwise, how is the list used?
What each one is built on
The structure explains the cost.
You do not need to memorise costs. If you remember what each class is built from, the costs follow.
| Class | Built on | So it is good at | And poor at |
|---|---|---|---|
ArrayList | A growing array | Reading by index | Inserting or removing in the middle |
LinkedList | Nodes with links | Changing the two ends | Reading by index |
ArrayDeque | A ring of slots | Working at both ends | Reading by index, searching |
HashMap, HashSet | Buckets and hashCode | Lookup and "is it in?" | Order, which it ignores |
LinkedHashMap, LinkedHashSet | Buckets plus a chain of links | Lookup, in a fixed order | A little extra memory |
TreeMap, TreeSet | A balanced sorted tree | Sorted order and nearest-key searches | Being as quick as a hash map |
PriorityQueue | A heap in an array | The best item, now | Searching, and printing in order |
Three questions in the guide match three structures: an array when position matters, buckets when a key matters, and a tree or heap when order or urgency matters. Almost every collection in Java is one of those with a different coat on.
The whole comparison
What each one keeps, and what each costs.
First, what each one keeps. The word null here means "no object", and some collections refuse it.
| Collection | Order kept | Repeats | null allowed |
|---|---|---|---|
ArrayList | Insertion order | Yes | Yes |
HashSet, HashMap | None promised | No (keys) | One null key |
LinkedHashSet, LinkedHashMap | Insertion order | No (keys) | One null key |
TreeSet, TreeMap | Sorted | No (keys) | No null key |
ArrayDeque | Insertion order | Yes | No |
PriorityQueue | Only the top is ordered | Yes | No |
Now what the main operations cost, for n items.
| Collection | Read by index or key | Add | Check contains | Remove |
|---|---|---|---|---|
ArrayList | O(1) | O(1) at the end, O(n) in the middle | O(n) | O(n) |
ArrayDeque | Ends only, O(1) | O(1) at either end | O(n) | O(1) at the ends |
HashMap, HashSet | O(1) on average | O(1) on average | O(1) on average | O(1) on average |
LinkedHashMap, LinkedHashSet | O(1) on average | O(1) on average | O(1) on average | O(1) on average |
TreeMap, TreeSet | O(log n) | O(log n) | O(log n) | O(log n) |
PriorityQueue | Top only, O(1) | O(log n) | O(n) | O(log n) for the top, O(n) otherwise |
Two habits fall out of the table. If you call contains in a loop on a list, you have n times n work, and a HashSet fixes it. If you keep sorting a list again and again, a TreeSet or a PriorityQueue may be the tool you wanted.
Three problems, choose first
Decide the collection, then write the code.
Problem 1
First letter that stands alone
Write firstUnique, which returns the index of the first character in the text that appears exactly once, or -1 if every character repeats. For "aabcb" it is 3, because c stands alone. For "aabb" it is -1.
Start here
static int firstUnique(String text) {
throw new UnsupportedOperationException("index of the first letter that appears only once, or -1");
}Paste into jshell to try it
Stuck?
Show a solution
static int firstUnique(String text) {
// Counting needs a lookup by letter: a HashMap.
Map<Character, Integer> counts = new HashMap<>();
for (char c : text.toCharArray()) {
counts.merge(c, 1, Integer::sum);
}
// The text itself keeps the order, so scan it again.
for (int i = 0; i < text.length(); i++) {
if (counts.get(text.charAt(i)) == 1) {
return i;
}
}
return -1;
}Paste into jshell to try it
Two passes, and two different tools. The HashMap is the right bag for counting, since each lookup and update is O(1) on average. But a hash map has no order, so the second pass walks the original text, which does. Together that is O(n) time and O(1) extra space for a fixed alphabet. A LinkedHashMap would keep the order too, but scanning the string is simpler.
Problem 2
An editor with undo
Complete the Editor class. type(more) adds text to the end. undo() takes back the most recent type and returns true, or returns false when there is nothing left to undo. text() returns the text so far. After typing "Hel", "lo" and " world", three undos get back to the empty text.
Start here
static class Editor {
void type(String more) {
throw new UnsupportedOperationException("add more to the end of the text");
}
boolean undo() {
throw new UnsupportedOperationException("go back one step; false if there is nothing to undo");
}
String text() {
throw new UnsupportedOperationException("the text so far");
}
}Paste into jshell to try it
Stuck?
Show a solution
static class Editor {
// Undo takes back the newest change first: a stack.
private final Deque<String> history = new ArrayDeque<>();
private String text = "";
void type(String more) {
history.push(text); // remember the text as it was before this change
text = text + more;
}
boolean undo() {
if (history.isEmpty()) {
return false;
}
text = history.pop();
return true;
}
String text() {
return text;
}
}Paste into jshell to try it
Undo is last in, first out, so a stack is the natural fit, and ArrayDeque is the recommended stack. Storing the whole previous text is the simplest design. It uses O(total length of all versions) memory, which is fine here. Real editors store only what changed. Every method is O(1) apart from building the new text.
Problem 3
The most common words
Write topWords, which returns the k most common words, most common first. Words with the same count are ordered A to Z. If there are fewer than k different words, return them all. For tea, chai, tea, coffee, chai, tea, milk and k = 2, the answer is [tea, chai].
Start here
static List<String> topWords(String[] words, int k) {
throw new UnsupportedOperationException("the k most common words; ties go A to Z");
}Paste into jshell to try it
Stuck?
Show a solution
static List<String> topWords(String[] words, int k) {
Map<String, Integer> counts = new HashMap<>();
for (String word : words) {
counts.merge(word, 1, Integer::sum);
}
// A map has no order, so move the entries into a list that we can sort.
List<Map.Entry<String, Integer>> entries = new ArrayList<>(counts.entrySet());
entries.sort((a, b) -> {
int byCount = Integer.compare(b.getValue(), a.getValue()); // bigger count first
return byCount != 0 ? byCount : a.getKey().compareTo(b.getKey()); // then A to Z
});
List<String> top = new ArrayList<>();
for (int i = 0; i < Math.min(k, entries.size()); i++) {
top.add(entries.get(i).getKey());
}
return top;
}Paste into jshell to try it
Each tool does the job it is built for. The HashMap counts in O(n). The list holds the entries so they can be sorted in O(m log m), where m is the number of different words. The comparator compares the counts the other way round, so bigger comes first, and falls back to comparing the words when the counts tie, which makes the answer the same every time. Without the tie-breaker the order of equal counts would depend on the map's unspecified order. A PriorityQueue of size k would be a good alternative for a very long word list.
Choosing in the interview
The question behind many questions.
"Which collection would you use?" is asked in nearly every Java interview, sometimes in disguise. The answer is never just a name. It is a name plus a reason.
- Say the need first. "I need to look up by ID, and order doesn't matter, so a
HashMap." One sentence with the need and the pick beats a list of facts. - "ArrayList or LinkedList?"
ArrayList, nearly always. Say why: reading by index is O(1), the items sit together in memory, andLinkedListneeds a node per item. - "How do you remove duplicates from a list?" Copy it into a
HashSet, or aLinkedHashSetif the order must stay. Say the cost: O(n). - "Stack or queue in Java?"
ArrayDequefor both, notStackand notLinkedList. - "HashMap, LinkedHashMap or TreeMap?" Unordered and fastest, insertion order, sorted. One sentence for each.
- "How would you keep the 10 largest of a million numbers?" A
PriorityQueueof size 10, as in lesson 9. - "Why
List<String> x = new ArrayList<>()?" Programming to the interface. The rest of the code depends only onList, so you can change the class in one place. - "What if the choice is wrong?" It rarely matters until the data is large. If a profile shows a hot
containson a list, switch to a set. Start simple, and measure before you tune. - Say the cost. "Index or key lookup: O(1) for arrays and hashes, O(log n) for trees. The word that matters is what I need to do most."
Updated 2026-10-17
Made by VeraStack Labs