Collections Framework

Reviewed & published by Brayan K

Master Java's powerful data structure library — the right collection choice can make your program 100x faster.

Part of the free Java course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

📚 Before You Start

What You'll Learn

1️⃣ Why Collections Matter

Real-world analogy: Collections are like different types of storage containers. An ArrayList is a numbered filing cabinet — great for ordered access. A HashMap is a dictionary — look up any word instantly. A HashSet is a bag of unique marbles — quickly check "is this marble in the bag?"

Why not just use arrays? Arrays have a fixed size. Collections resize dynamically, offer rich APIs (search, sort, filter), and provide type safety with generics.

2️⃣ ArrayList — Your Go-To List

Use ArrayList when you need ordered data with fast index access. It's the most commonly used collection.

List<String> names = new ArrayList<>();
names.add("Alice");              // add to end
names.add("Bob");
names.add(1, "Charlie");         // insert at index 1
names.get(0);                    // "Alice" — O(1)
names.set(0, "Alicia");          // replace at index
names.remove("Bob");             // remove by value
names.size();                    // current count
names.contains("Charlie");      // true

// Sort and iterate
Collections.sort(names);        // alphabetical
for (String name : names) {     // for-each
    System.out.println(name);
}

Performance: O(1) for get/set by index, O(n) for insert/remove in the middle.

import java.util.*;

public class Main {
    record Student(String name, int grade) {}

    public static void main(String[] args) {
        System.out.println("=== ArrayList Operations ===\n");

        // 1. Basic operations
        List<String> names = new ArrayList<>(List.of("Alice", "Bob", "Charlie"));
        System.out.println("1. Initial list: " + String.join(", ", names));

        names.add("Diana");
        System.out.println("   After add('Diana'): " + String.join(", ", names));

        names.add(1, "Eve");                 // insert at index 1
        System.out.println("   After add(1, 'Eve'): " + String.join(", ", names));

        System.out.println("   get(0): " + names.get(0));
        System.out.println("   size: " + names.size());
        System.out.println("   contains('Bob'): " + names.contains("Bob"));

        names.remove("Bob");
        System.out.println("   After remove('Bob'): " + String.join(", ", names));

        // 2. Sorting
        Collections.sort(names);
        System.out.println("\n2. Sorted: " + String.join(", ", names));

        // 3. Filtering with streams
        List<String> longNames = names.stream()
            .filter(n -> n.length() > 4)
            .toList();
        System.out.println("\n3. Names longer than 4 chars: " + String.join(", ", longNames));

        // 4. Transform with streams
        List<String> upper = names.stream()
            .map(String::toUpperCase)
            .toList();
        System.out.println("4. Uppercase: " + String.join(", ", upper));

        // 5. Student grade list, ranked descending
        System.out.println("\n5. Student Grade System:");
        List<Student> students = new ArrayList<>(List.of(
            new Student("Alice", 95),
            new Student("Bob", 82),
            new Student("Charlie", 91),
            new Student("Diana", 78),
            new Student("Eve", 88)
        ));
        students.sort(Comparator.comparingInt(Student::grade).reversed());
        System.out.println("   Ranked by grade:");
        for (int i = 0; i < students.size(); i++) {
            Student s = students.get(i);
            System.out.println("   " + (i + 1) + ". " + s.name() + ": " + s.grade());
        }
        double avg = students.stream().mapToInt(Student::grade).average().orElse(0);
        System.out.printf("   Average: %.1f%n", avg);
        System.out.println("   Top student: " + students.get(0).name());
    }
}

3️⃣ HashMap — Key-Value Lookups

Analogy: Like a phone book — you look up a person's name (key) to find their number (value). You don't search page by page; you jump directly to the right entry.

Map<String, Integer> scores = new HashMap<>();
scores.put("Alice", 95);
scores.put("Bob", 87);

scores.get("Alice");            // 95 — O(1) average!
scores.containsKey("Bob");      // true
scores.getOrDefault("Diana", 0); // 0 (safe default)

// Common pattern: counting frequency
Map<String, Integer> freq = new HashMap<>();
for (String word : words) {
    freq.put(word, freq.getOrDefault(word, 0) + 1);
}

4️⃣ HashSet — Unique Elements

Use HashSet when you need to track unique items. Duplicates are silently ignored.

Set<String> tags = new HashSet<>();
tags.add("java");
tags.add("oop");
tags.add("java");     // duplicate — silently ignored!
tags.size();          // 2
tags.contains("java"); // true — O(1)!

// Great for deduplication
List<String> withDupes = Arrays.asList("a", "b", "a", "c", "b");
Set<String> unique = new HashSet<>(withDupes); // {"a", "b", "c"}
import java.util.*;
import java.util.stream.Collectors;

public class Main {
    public static void main(String[] args) {
        System.out.println("=== HashMap & HashSet ===\n");

        // 1. HashMap — scores (LinkedHashMap keeps insertion order for printing)
        System.out.println("1. HashMap (Student Scores):");
        Map<String, Integer> scores = new LinkedHashMap<>();
        scores.put("Alice", 95);
        scores.put("Bob", 87);
        scores.put("Charlie", 92);
        scores.put("Diana", 88);
        System.out.println("   Alice: " + scores.get("Alice"));
        System.out.println("   Size: " + scores.size());
        System.out.println("   Has 'Bob': " + scores.containsKey("Bob"));
        System.out.println("   Unknown: " + scores.getOrDefault("Unknown", -1));

        // 2. Word frequency counter (merge is the idiomatic counting pattern)
        System.out.println("\n2. Word Frequency Counter:");
        String text = "java is great java is powerful java collections are useful collections are great";
        Map<String, Integer> freq = new HashMap<>();
        for (String w : text.split(" ")) {
            freq.merge(w, 1, Integer::sum);
        }
        // Sort by count descending, THEN by word — without that second key,
        // words sharing a count come out in whatever order the HashMap happened
        // to hold them, which differs between runs and JVM versions.
        freq.entrySet().stream()
            .sorted(Map.Entry.<String, Integer>comparingByValue().reversed()
                        .thenComparing(Map.Entry.comparingByKey()))
            .forEach(e -> System.out.println("   " + e.getKey() + ": " + e.getValue()));

        // 3. HashSet — unique tags
        System.out.println("\n3. HashSet (Unique Tags):");
        Set<String> tags = new LinkedHashSet<>();
        tags.add("java"); tags.add("oop"); tags.add("java"); tags.add("collections");
        System.out.println("   Tags: " + String.join(", ", tags));
        System.out.println("   Size: " + tags.size() + " (duplicate 'java' was ignored!)");

        // 4. Deduplication
        System.out.println("\n4. Deduplication:");
        List<String> dupes = List.of("apple", "banana", "apple", "cherry", "banana", "date");
        List<String> unique = new ArrayList<>(new LinkedHashSet<>(dupes));
        System.out.println("   Before: " + String.join(", ", dupes));
        System.out.println("   After: " + String.join(", ", unique));

        // 5. Group students by grade
        System.out.println("\n5. Group By Grade:");
        record Student(String name, String grade) {}
        List<Student> students = List.of(
            new Student("Alice", "A"), new Student("Bob", "B"),
            new Student("Charlie", "A"), new Student("Diana", "B"),
            new Student("Eve", "A")
        );
        Map<String, List<String>> grouped = students.stream()
            .collect(Collectors.groupingBy(Student::grade, TreeMap::new,
                     Collectors.mapping(Student::name, Collectors.toList())));
        grouped.forEach((grade, names) ->
            System.out.println("   Grade " + grade + ": " + String.join(", ", names)));
    }
}

5️⃣ Which Collection Should I Use?

NeedUseWhy
Ordered list, read by indexArrayListO(1) get, auto-resize
Frequent insert/remove at endsLinkedListO(1) add/remove at head/tail
Key → value mappingHashMapO(1) lookup by key
Sorted key → valueTreeMapKeys always sorted
Unique elements onlyHashSetO(1) contains check
LIFO (undo, backtracking)Deque/Stackpush/pop O(1)
FIFO (task queue, BFS)Queue/LinkedListoffer/poll O(1)

Default choice: Start with ArrayList for lists and HashMap for key-value pairs.

6️⃣ Common Beginner Mistakes

❌ Mistake 1: Modifying a collection while iterating

// CRASHES with ConcurrentModificationException!
for (String s : list) {
    if (s.equals("remove")) list.remove(s);
}

// CORRECT: use removeIf (Java 8+)
list.removeIf(s -> s.equals("remove"));

❌ Mistake 2: Using raw types (no generics)

List list = new ArrayList();     // BAD — no type safety
List<String> list = new ArrayList<>(); // GOOD

❌ Mistake 3: Not overriding hashCode() with HashMap keys

If you override equals(), you must also override hashCode().

import java.util.*;

public class Main {
    record Task(String name, int priority) {}

    public static void main(String[] args) {
        System.out.println("=== Stack & Queue ===\n");

        // 1. Stack (LIFO) via Deque — preferred over the legacy Stack class
        System.out.println("1. Browser History Stack:");
        Deque<String> history = new ArrayDeque<>();
        history.push("google.com");
        history.push("github.com");
        history.push("stackoverflow.com");
        System.out.println("   Current page: " + history.peek());
        System.out.println("   Back (pop): " + history.pop());
        System.out.println("   Now on: " + history.peek());

        // 2. Undo stack
        System.out.println("\n2. Text Editor Undo:");
        Deque<String> undoStack = new ArrayDeque<>();
        StringBuilder text = new StringBuilder();
        for (String chars : new String[]{"Hello", " World", "!"}) {
            undoStack.push(text.toString());
            text.append(chars);
            System.out.println("   Type '" + chars + "' -> '" + text + "'");
        }
        for (int i = 0; i < 2; i++) {
            text = new StringBuilder(undoStack.pop());
            System.out.println("   Undo -> '" + text + "'");
        }
        System.out.println("   Final: '" + text + "'");

        // 3. Queue (FIFO)
        System.out.println("\n3. Print Queue:");
        Queue<String> printQueue = new LinkedList<>();
        printQueue.offer("Report.pdf");
        printQueue.offer("Photo.jpg");
        printQueue.offer("Resume.docx");
        System.out.println("   Queue: " + String.join(", ", printQueue));
        while (!printQueue.isEmpty()) {
            System.out.println("   Printing: " + printQueue.poll());
        }

        // 4. Practical: priority-ordered task scheduler
        System.out.println("\n4. Task Scheduler:");
        PriorityQueue<Task> tasks = new PriorityQueue<>(Comparator.comparingInt(Task::priority));
        tasks.offer(new Task("Build UI", 2));
        tasks.offer(new Task("Write tests", 1));
        tasks.offer(new Task("Deploy", 3));
        System.out.println("   Tasks (by priority):");
        int completed = 0;
        while (!tasks.isEmpty()) {
            Task task = tasks.poll();
            System.out.println("   - " + task.name() + " (priority " + task.priority() + ")");
            completed++;
        }
        System.out.println("   Completed: " + completed + " tasks");
    }
}
import java.util.*;

public class Main {
    public static void main(String[] args) {
        // 🎯 YOUR TURN — replace each ___ using the hint beside it.

        // 1) A List keeps order and allows duplicates. Declare it by its
        //    interface, and create the resizable implementation.
        List<String> queue = new ___<>();        // 👉 replace ___ with ArrayList
        queue.add("Ada");
        queue.add("Ben");
        queue.add("Ada");
        System.out.println("Queue: " + queue);

        // 2) Lists report size() — arrays use length. Easy to mix up.
        System.out.println("Size: " + queue.___());   // 👉 replace ___ with size

        // 3) A Set drops duplicates. This one also keeps insertion order.
        Set<String> unique = new ___<>(queue);   // 👉 replace ___ with LinkedHashSet
        System.out.println("Unique: " + unique);

        // 4) A TreeMap keeps its keys sorted, unlike HashMap.
        Map<String, Integer> minutes = new ___<>();   // 👉 replace ___ with TreeMap
        minutes.put("Ada", 12);
        minutes.put("Ben", 7);
        System.out.println("Ada waited " + minutes.get("Ada"));

        // 5) get() on a missing key gives null. This one gives a fallback.
        System.out.println("Cara waited " + minutes.___("Cara", 0));  // 👉 replace ___ with getOrDefault
        System.out.println("By name: " + minutes);

        // remove(Object) takes out the FIRST match only, not every match.
        queue.remove("Ada");
        System.out.println("After one Ada leaves: " + queue);
    }
}

📋 Quick Reference

CollectionKey MethodsComplexity
ArrayListadd, get, set, remove, sizeO(1) get, O(n) insert
LinkedListaddFirst, addLast, pollO(1) ends, O(n) middle
HashMapput, get, containsKey, removeO(1) average
TreeMapput, get, firstKey, lastKeyO(log n) sorted
HashSetadd, contains, removeO(1) average
Stack/Dequepush, pop, peekO(1)

🎉 Lesson Complete!

You now know how to use Java's core collections — ArrayList, HashMap, HashSet, Stack, and Queue — and how to choose the right one based on your data access patterns.

Practice quiz

Which collection gives O(1) access by index and resizes automatically?

  • LinkedList
  • HashSet
  • ArrayList
  • TreeMap

Answer: ArrayList. ArrayList offers O(1) get by index and grows automatically.

Which collection stores key-value pairs with O(1) average lookup?

  • HashMap
  • ArrayList
  • HashSet
  • Stack

Answer: HashMap. HashMap maps keys to values with O(1) average lookup by key.

What happens when you add a duplicate element to a HashSet?

  • It throws an exception
  • It replaces all copies
  • It adds a second copy
  • It is silently ignored

Answer: It is silently ignored. A HashSet keeps only unique elements; duplicates are silently ignored.

Modifying a list with remove() inside a for-each loop typically throws...

  • NullPointerException
  • ConcurrentModificationException
  • IndexOutOfBoundsException
  • Nothing

Answer: ConcurrentModificationException. Structurally modifying a collection during for-each iteration throws ConcurrentModificationException; use removeIf instead.

Which collection should you use to keep keys always sorted?

  • TreeMap
  • HashMap
  • LinkedList
  • HashSet

Answer: TreeMap. TreeMap keeps its keys sorted (O(log n) operations).

If you override equals() on a class used as a HashMap key, you must also override...

  • toString()
  • compareTo()
  • hashCode()
  • clone()

Answer: hashCode(). Override hashCode() whenever you override equals(), or HashMap lookups break.

What does 'List list = new ArrayList();' (a raw type) sacrifice?

  • Nothing
  • Type safety
  • All methods
  • The import

Answer: Type safety. Raw types lose generic type safety; always parameterize, e.g. List<String>.

Which structure follows LIFO (Last-In-First-Out), good for undo?

  • Queue
  • TreeMap
  • HashSet
  • Stack/Deque

Answer: Stack/Deque. A Stack/Deque is LIFO — push/pop from the same end, ideal for undo.

After Deque history = new ArrayDeque<>(); push('a'); push('b'); push('c'); what does pop() return?

  • a
  • c
  • b
  • null

Answer: c. push adds to the top and pop removes from the top, so the last pushed ('c') comes out first.

A Queue (FIFO) removes elements in what order via poll()?

  • Last added first
  • Random order
  • First added first
  • Sorted order

Answer: First added first. A Queue is FIFO: poll() returns the earliest-added element first.

Continue this course