Java Tutorial
🔍

Java Collections Framework

Java Collections Framework

Every real Java program manages groups of objects — a list of orders, a set of unique emails, a map of product prices. Before the Collections Framework existed, developers used raw arrays for everything: fixed size, no built-in search, no sorting, no standard way to pass them between methods. The Collections Framework replaced all of that with a unified, interface-driven architecture that every Java developer uses every day.

Understanding it is not optional. Every Java interview touches it. Every production codebase depends on it.

What Is the Java Collections Framework?

The Java Collections Framework (JCF) is a set of interfaces, implementations, and algorithms in the java.util package that provides standard, reusable data structures for Java programs. It was introduced in Java 1.2 and has been the foundation of data management in Java ever since.

The framework gives you three things:

  • ›Interfaces — define what a collection can do (List, Set, Queue, Map)
  • ›Implementations — concrete classes you actually instantiate (ArrayList, HashMap, HashSet, TreeMap)
  • ›Algorithms — utility methods that work on any collection (Collections.sort(), Collections.min(), Collections.binarySearch())

The diagram below shows the complete hierarchy. Map is a separate branch because it stores key-value pairs rather than individual elements.

java.lang.Iterable
    └── java.util.Collection
            ├── java.util.List              (ordered, allows duplicates)
            │       ├── ArrayList           (resizable array — default choice)
            │       ├── LinkedList          (doubly-linked nodes)
            │       └── Vector              (legacy, synchronized)
            │
            ├── java.util.Set               (unique elements, no duplicates)
            │       ├── HashSet             (hash table — fastest, no order)
            │       ├── LinkedHashSet       (hash table + insertion order)
            │       └── TreeSet             (Red-Black tree — sorted order)
            │
            └── java.util.Queue             (FIFO — first in, first out)
                    ├── PriorityQueue       (heap — priority-based order)
                    ├── ArrayDeque          (double-ended queue — modern default)
                    └── LinkedList          (also implements Queue)

java.util.Map                               (key-value pairs — separate hierarchy)
        ├── HashMap                         (hash table — fastest, no order)
        ├── LinkedHashMap                   (hash table + insertion order)
        ├── TreeMap                         (Red-Black tree — sorted keys)
        └── Hashtable                       (legacy, synchronized)

The Three Core Questions

Before writing a single line of collection code, answer these three questions in order:

Question 1: Do I need key-value lookup?
  YES → Use a Map  (HashMap, TreeMap, LinkedHashMap)
  NO  → Go to Question 2

Question 2: Do I need uniqueness — no duplicates?
  YES → Use a Set  (HashSet, TreeSet, LinkedHashSet)
  NO  → Go to Question 3

Question 3: Do I need an ordered sequence?
  YES → Use a List (ArrayList, LinkedList)
  Or a Queue for FIFO processing (ArrayDeque, PriorityQueue)

This decision tree produces the right collection family for almost every real-world scenario. The implementation choice — ArrayList vs LinkedList, HashMap vs TreeMap — comes after the family is decided.

Why Not Just Use Arrays?

A plain Java array works for fixed, known data. For everything else it creates problems that the Collections Framework solves directly.

// File: ArrayLimitationsDemo.java import java.util.ArrayList; import java.util.Collections; import java.util.List; public class ArrayLimitationsDemo { public static void main(String[] args) { System.out.println("=== Problem 1: Arrays have a fixed size ==="); // Array — must declare size at creation String[] fixedOrders = new String[3]; fixedOrders[0] = "ORD-001"; fixedOrders[1] = "ORD-002"; fixedOrders[2] = "ORD-003"; // fixedOrders[3] = "ORD-004"; — compile error! ArrayIndexOutOfBoundsException // ArrayList — grows automatically List<String> orders = new ArrayList<>(); orders.add("ORD-001"); orders.add("ORD-002"); orders.add("ORD-003"); orders.add("ORD-004"); // no problem — list handles the growth orders.add("ORD-005"); System.out.println("Array size (fixed): 3"); System.out.println("List size (dynamic): " + orders.size()); System.out.println(); System.out.println("=== Problem 2: Arrays have no built-in algorithms ==="); // Searching an array — write a manual loop String target = "ORD-003"; boolean found = false; for (String order : fixedOrders) { if (target.equals(order)) { found = true; break; } } System.out.println("Manual array search: found=" + found); // List — one method call System.out.println("List contains(): found=" + orders.contains("ORD-003")); // Sorting an array — Arrays.sort() works but produces no new API // Sorting a List — Collections.sort() + reverse, shuffle, min, max... Collections.sort(orders); System.out.println("Sorted list: " + orders); System.out.println("Min order : " + Collections.min(orders)); } }
Output:
=== Problem 1: Arrays have a fixed size ===
Array size (fixed): 3
List size (dynamic): 5

=== Problem 2: Arrays have no built-in algorithms ===
Manual array search: found=true
List contains():     found=true
Sorted list: [ORD-001, ORD-002, ORD-003, ORD-004, ORD-005]
Min order  : ORD-001

When to Use the Collections Framework

Use collections whenever the data you manage has any of these characteristics:

  • ›The size is not known at compile time
  • ›Elements need to be added or removed dynamically
  • ›Fast searching, sorting, or membership testing is needed
  • ›The data must be passed across method boundaries with a clean API
  • ›Multiple threads need to access the same data structure

Use a plain array only for: fixed-size primitive data where boxing overhead matters (an int[] of 1,000,000 numbers is faster and smaller than List<Integer>), low-level interoperability with APIs that require arrays, or multi-dimensional grid data (int[][]).

The Core Interfaces — What Each One Promises

List — Ordered Sequence

List<E> maintains insertion order and allows duplicate elements. Every List implementation provides index-based access through get(int index).

// File: ListBasicsDemo.java import java.util.ArrayList; import java.util.List; public class ListBasicsDemo { public static void main(String[] args) { // ArrayList is the default List implementation List<String> techStack = new ArrayList<>(); techStack.add("Java"); techStack.add("Spring Boot"); techStack.add("MySQL"); techStack.add("Java"); // duplicates allowed techStack.add(1, "Hibernate"); // insert at index 1 — shifts others right System.out.println("Tech stack : " + techStack); System.out.println("Size : " + techStack.size()); System.out.println("Element at [0] : " + techStack.get(0)); // index access System.out.println("Index of Java : " + techStack.indexOf("Java")); // first occurrence System.out.println("Contains MySQL : " + techStack.contains("MySQL")); // Remove by index vs by value techStack.remove(0); // removes "Java" at index 0 System.out.println("After remove(0): " + techStack); techStack.remove("Java"); // removes first occurrence of "Java" by value System.out.println("After remove(Java): " + techStack); } }
Output:
Tech stack     : [Java, Hibernate, Spring Boot, MySQL, Java]
Size           : 5
Element at [0] : Java
Index of Java  : 0
Contains MySQL : true
After remove(0): [Hibernate, Spring Boot, MySQL, Java]
After remove(Java): [Hibernate, Spring Boot, MySQL]

Set — Unique Elements

Set<E> automatically rejects duplicates. Adding an element that already exists returns false and leaves the set unchanged. No index — you cannot call get(0) on a Set.

// File: SetBasicsDemo.java import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Set; import java.util.TreeSet; public class SetBasicsDemo { public static void main(String[] args) { // HashSet — fastest, no guaranteed order Set<String> uniqueTags = new HashSet<>(); uniqueTags.add("java"); uniqueTags.add("backend"); uniqueTags.add("java"); // duplicate — silently ignored uniqueTags.add("spring"); uniqueTags.add("backend"); // duplicate — silently ignored System.out.println("HashSet size : " + uniqueTags.size()); // 3, not 5 System.out.println("HashSet contents: " + uniqueTags); // order not predictable System.out.println("add() duplicate : " + uniqueTags.add("java")); // returns false System.out.println(); // LinkedHashSet — no duplicates, preserves insertion order Set<String> orderedUnique = new LinkedHashSet<>(); orderedUnique.add("java"); orderedUnique.add("backend"); orderedUnique.add("spring"); orderedUnique.add("java"); // ignored System.out.println("LinkedHashSet: " + orderedUnique); // insertion order preserved System.out.println(); // TreeSet — no duplicates, sorted order Set<Integer> sortedScores = new TreeSet<>(); sortedScores.add(85); sortedScores.add(92); sortedScores.add(78); sortedScores.add(92); // ignored sortedScores.add(65); System.out.println("TreeSet (sorted): " + sortedScores); // always ascending } }
Output:
HashSet size    : 3
HashSet contents: [spring, backend, java]
add() duplicate : false

LinkedHashSet: [java, backend, spring]

TreeSet (sorted): [65, 78, 85, 92]

Map — Key-Value Pairs

Map<K,V> stores associations between unique keys and values. Given a key, you can retrieve its value in O(1) average time with HashMap — far faster than searching a list.

// File: MapBasicsDemo.java import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import java.util.TreeMap; public class MapBasicsDemo { public static void main(String[] args) { // HashMap — fastest, no guaranteed key order Map<String, Double> productPrices = new HashMap<>(); productPrices.put("Laptop", 45999.0); productPrices.put("Mouse", 599.0); productPrices.put("Keyboard", 1299.0); productPrices.put("Monitor", 12999.0); productPrices.put("Laptop", 43999.0); // replaces the existing value System.out.println("Price of Laptop : Rs." + productPrices.get("Laptop")); System.out.println("Price of Webcam : " + productPrices.get("Webcam")); // null — not found System.out.println("Size (unique keys): " + productPrices.size()); System.out.println("Contains 'Mouse' : " + productPrices.containsKey("Mouse")); System.out.println(); // Iterate all key-value pairs System.out.println("=== Product catalogue ==="); for (Map.Entry<String, Double> entry : productPrices.entrySet()) { System.out.printf(" %-12s Rs.%.2f%n", entry.getKey(), entry.getValue()); } System.out.println(); // TreeMap — keys in sorted order Map<String, Integer> wordCount = new TreeMap<>(); wordCount.put("java", 15); wordCount.put("spring", 8); wordCount.put("backend", 12); wordCount.put("api", 20); System.out.println("TreeMap (sorted keys): " + wordCount); // LinkedHashMap — keys in insertion order Map<String, String> config = new LinkedHashMap<>(); config.put("host", "localhost"); config.put("port", "8080"); config.put("db", "myapp_db"); System.out.println("LinkedHashMap (insertion order): " + config); } }
Output:
Price of Laptop : Rs.43999.0
Price of Webcam : null
Size (unique keys): 4
Contains 'Mouse'  : true

=== Product catalogue ===
  Laptop       Rs.43999.00
  Mouse        Rs.599.00
  Keyboard     Rs.1299.00
  Monitor      Rs.12999.00

TreeMap (sorted keys): {api=20, backend=12, java=15, spring=8}
LinkedHashMap (insertion order): {host=localhost, port=8080, db=myapp_db}

Queue — FIFO Processing

Queue<E> models a waiting line. Elements enter at the tail, leave from the head. ArrayDeque is the recommended default implementation.

// File: QueueBasicsDemo.java import java.util.ArrayDeque; import java.util.PriorityQueue; import java.util.Queue; public class QueueBasicsDemo { public static void main(String[] args) { // ArrayDeque as Queue — FIFO order Queue<String> taskQueue = new ArrayDeque<>(); taskQueue.offer("Send welcome email"); // adds to tail taskQueue.offer("Create user profile"); taskQueue.offer("Assign onboarding task"); System.out.println("Queue: " + taskQueue); System.out.println("peek() — next to process: " + taskQueue.peek()); // no removal System.out.println("poll() — process task : " + taskQueue.poll()); // removes head System.out.println("Queue after poll: " + taskQueue); System.out.println(); // PriorityQueue — processes highest-priority first Queue<Integer> urgencyQueue = new PriorityQueue<>(); // natural order: smallest first urgencyQueue.offer(3); // medium priority urgencyQueue.offer(1); // highest priority urgencyQueue.offer(5); // lowest priority urgencyQueue.offer(2); System.out.print("Processing by priority: "); while (!urgencyQueue.isEmpty()) { System.out.print(urgencyQueue.poll() + " "); // always returns smallest } System.out.println(); } }
Output:
Queue: [Send welcome email, Create user profile, Assign onboarding task]
peek() — next to process: Send welcome email
poll() — process task    : Send welcome email
Queue after poll: [Create user profile, Assign onboarding task]

Processing by priority: 1 2 3 5

Internal Working of Key Implementations

Understanding how each collection stores data internally is what makes you dangerous in technical interviews. You stop guessing and start reasoning.

ARRAYLIST — Backed by Object[]
  elementData: [ "A" | "B" | "C" | null | null | null | null | null | null | null ]
                  0      1      2                                               9
  size=3, capacity=10
  Growth: newCapacity = oldCapacity + (oldCapacity >> 1)  → roughly 1.5x

HASHSET / HASHMAP — Backed by hash table (array of buckets)
  table[0]: null
  table[1]: Entry("Laptop", 45999) → null
  table[2]: null
  table[3]: Entry("Mouse",  599)   → Entry("Keyboard", 1299) → null  (collision chain)
  ...
  get(key): compute hash → find bucket → compare with equals() → return value
  Average O(1) for get/put/contains

TREESET / TREEMAP — Backed by Red-Black tree (self-balancing BST)
              5(B)
            /      \
          3(R)     7(R)
         /   \   /    \
       1(B) 4(B)6(B)  8(B)
  All keys always in sorted order — in-order traversal gives sorted sequence
  Guaranteed O(log n) for get/put/contains regardless of insertion order

LINKEDLIST — Backed by doubly-linked Node chain
  null ← [prev|A|next] ↔ [prev|B|next] ↔ [prev|C|next] → null
            ↑                                    ↑
          first                                last
  addFirst / addLast: O(1) — update pointer only
  get(index): O(n) — must traverse from head or tail

Basic Example

import java.util.ArrayDeque; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Queue; import java.util.Set; public class Main { public static void main(String[] args) { List<String> list = new ArrayList<>(); list.add("Task 1"); list.add("Task 1"); // duplicates allowed Set<String> set = new HashSet<>(); set.add("Task 1"); set.add("Task 1"); // duplicate ignored Map<String, Integer> map = new HashMap<>(); map.put("Task 1", 1); Queue<String> queue = new ArrayDeque<>(); queue.offer("Task 1"); queue.offer("Task 2"); System.out.println("List size: " + list.size()); System.out.println("Set size: " + set.size()); System.out.println("Map lookup: " + map.get("Task 1")); System.out.println("Queue processes first: " + queue.poll()); } }

Output

List size: 2
Set size: 1
Map lookup: 1
Queue processes first: Task 1

How it works:

  1. ›List keeps every element you add, including duplicates — its size grows to 2 even though "Task 1" was added twice.
  2. ›Set silently rejects the second "Task 1" — its size stays at 1, since uniqueness is its defining contract.
  3. ›Map associates a key with a value for O(1) average lookup — get("Task 1") returns its value directly instead of scanning.
  4. ›Queue processes elements in the order they arrived — poll() returns "Task 1" first because it was offered first (FIFO).
  5. ›Each of the four families — List, Set, Map, Queue — solves a different problem; picking the right one is the first decision the Collections Framework asks you to make.

Performance Considerations

Collectionget / lookupadd (end)removecontainsOrder
ArrayListO(1) by indexO(1) amortO(n) shiftO(n)Insertion
LinkedListO(n) traverseO(1) head/tailO(1) at endsO(n)Insertion
HashSet—O(1) avgO(1) avgO(1) avgNone
LinkedHashSet—O(1) avgO(1) avgO(1) avgInsertion
TreeSet—O(log n)O(log n)O(log n)Sorted
HashMapO(1) avg by keyO(1) avgO(1) avgO(1) avg keyNone
LinkedHashMapO(1) avg by keyO(1) avgO(1) avgO(1) avg keyInsertion
TreeMapO(log n) by keyO(log n)O(log n)O(log n)Sorted
ArrayDequeO(1) head/tailO(1) amortO(1) endsO(n)FIFO/LIFO
PriorityQueueO(1) peek minO(log n)O(log n)O(n)Priority

Thread safety: None of the standard implementations above are thread-safe. For concurrent use: ConcurrentHashMap (high-performance concurrent Map), CopyOnWriteArrayList (read-heavy concurrent List), Collections.synchronizedList(list) (simple single-lock wrapper).

Best Practices

Always declare the variable type as the interface, not the implementation. Write List<String> names = new ArrayList<>() rather than ArrayList<String> names = new ArrayList<>(). This lets you swap ArrayList for LinkedList or CopyOnWriteArrayList by changing one line, not every method signature. This is the first thing seniors check in code reviews.

Choose the collection based on the primary access pattern, not the data shape. A list of orders sounds like an ArrayList, but if the main operation is "is this order ID already processed?", you need a HashSet<String> for O(1) lookup. The collection serves the algorithm, not the domain model.

Use Map.getOrDefault() and Map.merge() instead of null checks. map.getOrDefault(key, 0) eliminates the if (map.get(key) == null) boilerplate. map.merge(key, 1, Integer::sum) is the idiomatic way to build a frequency counter — it handles both the first occurrence and subsequent increments in one atomic call.

Pre-size collections for bulk loads. new ArrayList<>(10_000) and new HashMap<>(10_000) prevent repeated internal array resizing when you know roughly how many elements to expect. Each resize copies all existing elements — five resizes on a 10,000-element list means copying 50,000+ element references unnecessarily.

Common Mistakes

Mistake 1 — Using List.contains() in a Loop for Large Data

// O(n²) — contains scans the entire list on every call List<String> processedIds = new ArrayList<>(); for (String id : incomingIds) { if (!processedIds.contains(id)) { // O(n) per call processedIds.add(id); process(id); } } // O(n) — HashSet.add() returns false for duplicates; O(1) per call Set<String> processedIds = new HashSet<>(); for (String id : incomingIds) { if (processedIds.add(id)) { // returns false if already present process(id); } }

Mistake 2 — Modifying a List Inside a for-each Loop

List<String> items = new ArrayList<>(List.of("A", "B", "C", "D")); // Throws ConcurrentModificationException at runtime for (String item : items) { if (item.equals("B")) { items.remove(item); // modifies list while iterator is running } } // Correct — removeIf handles the bookkeeping internally items.removeIf("B"::equals);

Mistake 3 — Using == Instead of equals() for Map Key Lookup

Map<String, Integer> scores = new HashMap<>(); scores.put("Priya", 95); String key = new String("Priya"); // different object, same content // WRONG — always false for objects; == compares references, not content if (key == "Priya") { ... } // CORRECT — HashMap uses equals() internally; this works as expected System.out.println(scores.get(key)); // 95 — equals() comparison System.out.println(scores.containsKey(key)); // true

Mistake 4 — Iterating keySet() and Calling get() Per Key

Map<String, Integer> map = new HashMap<>(Map.of("A", 1, "B", 2, "C", 3)); // Inefficient — two lookups per entry (keySet traversal + get) for (String key : map.keySet()) { System.out.println(key + " = " + map.get(key)); // get() is an extra O(1) call } // Correct — entrySet gives both key and value in a single traversal for (Map.Entry<String, Integer> entry : map.entrySet()) { System.out.println(entry.getKey() + " = " + entry.getValue()); }

Interview Questions

Q1. What is the Java Collections Framework?

The Java Collections Framework is a set of interfaces, implementations, and algorithms in java.util for managing groups of objects. It provides standard data structures — ArrayList, HashMap, HashSet, TreeMap, ArrayDeque — that every Java application uses. The framework introduced a unified interface hierarchy: Collection → List/Set/Queue, with Map as a separate branch for key-value pairs. Before the framework existed in Java 1.2, developers built their own data structures or used the limited, inconsistent Vector and Hashtable classes.

Q2. What is the difference between List, Set, and Map?

List is an ordered sequence that allows duplicate elements and supports index-based access — ArrayList.get(0) retrieves the first element. Set is a collection that rejects duplicates — adding the same element twice has no effect and add() returns false. Map stores key-value pairs where keys are unique — HashMap.put("key", value) replaces any existing value for that key. Map does not extend Collection because its key-value structure does not fit the single-element add(E) contract. The practical decision rule: key-value lookup → Map, uniqueness required → Set, ordered sequence → List.

Q3. What is the difference between ArrayList and LinkedList?

Both implement List. ArrayList stores elements in a contiguous Object[] array — O(1) random access by index and cache-friendly sequential iteration. LinkedList stores each element in a Node with prev/next references — O(1) insertion and deletion at the head and tail, but O(n) for index-based access because there is no array to jump into. In practice, ArrayList outperforms LinkedList for the vast majority of operations due to CPU cache locality. Choose LinkedList only when insertions and removals at both ends of a large list are the primary operation.

Q4. What is the difference between HashMap and TreeMap?

HashMap stores entries in a hash table — O(1) average for get(), put(), and remove(), with no guaranteed key iteration order. TreeMap stores entries in a Red-Black tree — O(log n) for all operations, but keys are always iterated in sorted natural order. TreeMap also offers navigation methods (floorKey(), ceilingKey(), headMap(), tailMap()) that HashMap does not. Choose HashMap for pure key-value lookup; choose TreeMap when sorted key iteration or range queries are needed.

Q5. How does HashSet prevent duplicates?

HashSet is backed internally by a HashMap. When you call set.add(element), it calls map.put(element, PRESENT) on the backing map. HashMap determines key uniqueness using hashCode() first (to find the bucket) and then equals() (to find an exact match within the bucket). If the key already exists, the put returns the old value and the element is considered a duplicate. This is why any class used in a HashSet must override both hashCode() and equals() consistently — if two logically equal objects have different hash codes, they land in different buckets and the set stores both, violating uniqueness.

Q6. What is the Collections utility class and how does it differ from the Collection interface?

java.util.Collection (singular) is an interface — the root of the collection hierarchy, extended by List, Set, and Queue. It defines the contract: add(), remove(), contains(), size(), iterator(). java.util.Collections (plural) is a final utility class with only static methods — it has no instances. It provides algorithms: sort(), reverse(), shuffle(), binarySearch(), min(), max(), frequency(), unmodifiableList(), synchronizedList(). The confusion between the two names is one of the most common beginner questions and a reliable interview gotcha at service-based companies.

FAQs

What is the best collection to use for most situations in Java?

The defaults work for the vast majority of real code: use ArrayList for ordered lists, HashSet for unique element collections, and HashMap for key-value lookups. These three cover perhaps 90% of all collection usage in typical Java applications. Reach for LinkedHashMap when insertion order matters in a map, TreeMap when sorted key iteration is needed, and ConcurrentHashMap when multiple threads share a map.

What is the difference between Collection.remove() and Collections.remove()?

Collection.remove(Object o) is an instance method on any collection — it removes the first occurrence of the specified element from that collection. Collections (the utility class) does not have a remove() method at all. The utility class provides Collections.sort(), Collections.min(), Collections.unmodifiableList(), and similar algorithms. The two are unrelated beyond sharing most of their name.

Why does Map not extend Collection in Java?

Collection<E> models a group of individual elements with a single add(E element) method. Map<K,V> models associations between keys and values — every entry is inherently two things. Forcing Map to implement add(element) does not make semantic sense for a key-value store. Instead, Map forms its own parallel hierarchy. You access collection views of a map using keySet() (returns Set<K>), values() (returns Collection<V>), and entrySet() (returns Set<Map.Entry<K,V>>), each of which participates normally in the Collection hierarchy.

How do I iterate a HashMap in Java?

Three patterns: map.entrySet() to iterate both key and value together (for (Map.Entry<K,V> e : map.entrySet())), map.keySet() to iterate keys only, and map.values() to iterate values only. The entry set approach is most efficient when both key and value are needed — it avoids the extra map.get(key) call per iteration that the key set approach requires. Java 8+ adds map.forEach((k, v) -> ...) as a cleaner lambda-based alternative.

What is the difference between fail-fast and fail-safe iterators?

Fail-fast iterators — used by ArrayList, HashMap, HashSet — throw ConcurrentModificationException if the collection is structurally modified while iteration is in progress. They detect this via an internal modCount counter. Fail-safe iterators — used by CopyOnWriteArrayList and ConcurrentHashMap — do not throw. CopyOnWriteArrayList iterates a snapshot of the array taken at iterator creation time. ConcurrentHashMap uses a weakly consistent iterator that may or may not reflect concurrent modifications.

Can two equal keys exist in a HashMap?

No. HashMap guarantees key uniqueness. Two keys are considered equal if hashCode() produces the same bucket index and equals() returns true. If you put() with a key that already exists, the new value replaces the old one and put() returns the old value. This is why implementing equals() without implementing hashCode() (or vice versa) breaks HashMap — two logically equal objects with different hash codes land in different buckets and are both stored as if they were different keys.

What is the difference between Comparable and Comparator in Collections?

Comparable<T> is implemented by the class itself and defines the natural ordering through compareTo(T other). It is used automatically by Collections.sort(), TreeSet, and TreeMap when no external ordering is provided. Comparator<T> is a separate strategy class that defines an external ordering — useful when you need multiple sort orders for the same class, or when the class is not yours to modify. Java 8 made Comparator a functional interface, so it can be written as a lambda: Comparator.comparing(Product::getPrice).

Summary

The Java Collections Framework is the backbone of data management in Java. The hierarchy has two branches: Collection (List, Set, Queue) for individual elements, and Map for key-value pairs. The three-question decision process — key-value? → Map; unique? → Set; otherwise → List — points to the right collection family every time. The implementation choice follows: Hash variants for O(1) speed with no order, Linked variants for insertion order, Tree variants for sorted order.

The defaults cover most code: ArrayList, HashSet, HashMap. The specialised types — TreeMap, PriorityQueue, CopyOnWriteArrayList, ConcurrentHashMap — each solve a specific problem and should be chosen deliberately, not by default.

For interviews: know the hierarchy by heart, understand why Map does not extend Collection, be able to explain HashSet's duplicate prevention through hashCode() and equals(), and always know the time complexity of the operations you are using. These are the questions that open the rest of the collections discussion at every company from TCS to Flipkart.

What to Read Next

Practice Programs

Put this topic into practice — each program has full code, output and a step-by-step explanation you can run online.

Browse all Java programs →