Lessons available in both languages
Java Backend · Interview Prep

Collections and Generics interview questions & answers

211+ real Collections and Generics interview questions with model answers, plus free lessons to learn the concepts. Prepare in English & Hinglish, then practise with an AI mock interview.

18 topics · 211+ questions

How Hirenix teaches

One chapter. 90 minutes.
Interview-ready.

Every concept starts with a real-world problem — the kind that actually shows up in production code. Nothing to cram; it just clicks. Every question comes with a model answer: exactly what to say in the room, and why. Then an AI mock interview on the same chapter.

  • 📖Concept in 5 minutesNo jargon — straight to the point
  • 🛠️Real-world problemThe kind production code throws at you
  • 💬Model answerExactly what to say in the room
  • 🧠FlashcardsRevise in 10 minutes
  • 🤖AI mock interviewIt asks follow-ups too
  • 📊Weak topicsSee exactly where you're stuck
Start this chapter — free🌐 English🇮🇳 Hinglish
A student learning an interview concept on Hirenix at home
Video playlistbuilt around a syllabus18h+
Hirenix chapterbuilt around interviews90 min

The difference isn’t the content — it’s the filter. Only what’s actually used in production and actually asked in interviews. Textbook topics the industry never touches don’t make the cut.

Lessons available in both languages

What you’ll learn

  • Collections framework overview
  • List: ArrayList vs LinkedList
  • Set: HashSet, LinkedHashSet, TreeSetFree account
  • Map and HashMap basicsFree account
  • How HashMap works internallyFree account
  • Ordering: LinkedHashMap and TreeMapFree account
  • Queue, Deque and PriorityQueueFree account
  • Iterators and ConcurrentModificationExceptionFree account
  • Sorting: Comparable and ComparatorFree account
  • Collections utility and immutable viewsFree account
  • Generics basicsFree account
  • Wildcards and type erasureFree account
  • Enums in JavaFree account
  • Choosing the right collectionFree account
  • RecapFree account
  • Project: Student Gradebook
  • Project: Word Frequency AnalyzerFree account
  • Project: Inventory ManagerFree account

Collections framework overview

Think of a kirana shop. The owner keeps three different things behind the counter, and each one has a shape that fits its job.

There is the daily sales register — every sale written in the order it happened, the same customer appearing four times if they came four times. There is the list of shops on the street — each shop named once, because writing "Sharma General Store" twice tells you nothing new. And there is the rate card — you never read it front to back, you look up sugar and get 48.

Nobody designed three registers to be clever. Each one exists because a different question gets asked of it.

In production those three shapes are Java's three collection families. The sales register is a List — ordered, duplicates allowed, reached by position. The street list is a Set — no duplicates, and it does not promise any order. The rate card is a Map — you hold a key and you get a value.

🌍 Real-world example: a food delivery app holds your order history in a List, the set of cuisines it can filter by in a Set, and restaurant-id to restaurant-details in a Map. Same data source, three shapes, because three different questions.

💡 Collection Framework = the ready-made set of data structures in java.util (List, Set, Queue, Map and their implementations) plus the interfaces that make them interchangeable. 💡 Iterable = the interface at the very top. Anything that is Iterable can be used in a for-each loop. 💡 Collection (no s) = the interface below Iterable, extended by List, Set and Queue. 💡 Collections (with s) = a completely separate utility class of static helper methods like Collections.sort, Collections.max, Collections.unmodifiableList.

The hierarchy, in the order it actually matters

Iterable
   └── Collection ......... add, remove, size, contains, iterator
         ├── List ......... ordered, duplicates OK, index access    -> ArrayList, LinkedList, Vector
         ├── Set .......... no duplicates                           -> HashSet, LinkedHashSet, TreeSet
         └── Queue ........ processing order                        -> ArrayDeque, PriorityQueue

Map .................. key -> value, NOT under Collection           -> HashMap, LinkedHashMap, TreeMap

Two things in that diagram get asked about constantly.

Map hangs off to the side. It is part of the framework but it does not extend Collection. The reason is mechanical, not philosophical: Collection promises add(E element) — one thing at a time. A Map stores pairs, so its method is put(K key, V value). A shape that needs two arguments cannot honour a contract written for one. Saying "a Map is a Collection" is the single most common hierarchy mistake.

Collection and Collections are unrelated. One is the interface your ArrayList implements. The other is a toolbox class you never instantiate. The names differ by one letter, which is precisely why interviewers enjoy the question.

Why the framework exists at all — the array it replaced

Before collections you had arrays, and an array has three hard edges:

  • Its length is fixed at creation. arr[3] on a length-3 array throws ArrayIndexOutOfBoundsException; growing it means allocating a bigger array and copying by hand.
  • It has no behaviour. No contains, no remove, no sort of its own — you write the loop.
  • Every array is its own island. A method that takes an array cannot take a List; a method that takes a List accepts ArrayList, LinkedList, or anything else that implements it.

That last point is the real prize. Because List is an interface, you write List<String> names = new ArrayList<>(); and every method downstream depends on the interface. Swap in a LinkedList a year later and nothing else changes.

Standard definition: The Java Collection Framework is a unified architecture of interfaces (Collection, List, Set, Queue, Map) and their implementations that provides ready-made, resizable data structures along with algorithms such as sorting and searching, so that different collection types can be used interchangeably through a common API.

When to use it: essentially always, once you are storing more than a couple of values. Reach for a List when order matters or duplicates are legitimate, a Set when the data is conceptually a set of unique things and you keep writing "have I already seen this", and a Map when you find yourself searching one list to look something up in another — that search is a Map waiting to be written.

When NOT to use it: a raw array still wins in two narrow cases. Primitives — a Collection can only hold objects, so List<Integer> boxes every int and costs memory that int[] does not. And a fixed-length buffer where the size is genuinely constant and the code is performance-critical, such as image or byte processing. Trade-off: collections cost a little memory and indirection, and they buy you resizing, ready-made algorithms and interchangeability. For ordinary business code that trade is not close.

import java.util.*;

public class T1 {
    public static void main(String[] args) {
        String[] arr = {"Aarav", "Diya", "Aarav"};
        // arr[3] = "Kabir";  // ArrayIndexOutOfBoundsException - array ki length badalti nahi

        List<String> list = new ArrayList<>(Arrays.asList(arr));
        list.add("Kabir");                          // List badh gayi, duplicate bhi rakhti hai
        System.out.println("List  : " + list + "  size=" + list.size());

        Set<String> set = new HashSet<>(list);      // duplicate khud hat gaya
        System.out.println("Set   : " + set + "  size=" + set.size());

        Map<String, Integer> map = new HashMap<>(); // key se dhoondhna
        map.put("Aarav", 91);
        map.put("Diya", 78);
        map.put("Aarav", 95);                       // wahi key dobara = value replace
        System.out.println("Map   : " + map + "  Aarav=" + map.get("Aarav"));

        Collections.sort(list);                     // Collections = utility class, List nahi
        System.out.println("sorted: " + list);
        System.out.println("max   : " + Collections.max(list));
        System.out.println("List is a Collection? " + (list instanceof Collection));
        System.out.println("Map is a Collection?  " + (map instanceof Collection));
    }
}

List: ArrayList vs LinkedList

Two ways to keep a stack of forms in an office.

One clerk keeps them in a bound register — every page numbered, every page in place. Ask for page 47 and their thumb lands on it. But insert a new page between 12 and 13 and every page after it has to be renumbered and shifted.

The other keeps loose sheets in a chain, each sheet carrying a note saying the next one is on that table. Slipping a new sheet into the middle is trivial — rewrite two notes. But "give me sheet 47" means starting at sheet 1 and following the chain forty-seven times.

Neither is better. They are opposite trades, and Java ships both.

In production the register is ArrayList — a resizable array underneath. The chain is LinkedList — a doubly linked chain of nodes.

🌍 Real-world example: a product listing that you page through and jump around in is an ArrayList. A job queue where work is constantly added and removed at the ends is closer to a LinkedList — though in practice ArrayDeque wins that one, which the queue topic covers.

💡 ArrayList = List backed by one array; index access is direct, insert in the middle shifts elements. 💡 LinkedList = List backed by nodes; each node holds the value plus links to the previous and next node. 💡 RandomAccess = an empty marker interface implemented by ArrayList and not by LinkedList, meaning "reaching any position here is cheap". 💡 Vector = the original synchronized List from Java 1.0, kept only for old code.

The measurement, not the opinion

The demo below builds both lists with 100,000 elements and times two jobs. The numbers are from a real run on JDK 17:

Job ArrayList LinkedList
get(i) 100,000 times 1 ms 5315 ms
add(0, x) 20,000 times 458 ms 7 ms

Five thousand times slower one way, sixty-five times faster the other. This is the whole topic, and it is why the answer to "which is faster" is always "at what?".

The half-answer that gets caught

Most candidates say "LinkedList is faster for insertion and deletion". The interviewer's follow-up is "faster at list.add(5000, x)?" — and there the answer flips, because the LinkedList must walk to position 5000 first. Its cheap insert only applies once you are already standing at the position: at the ends, or while holding an Iterator.

Say it precisely: LinkedList is cheap to insert at a position you already hold; it is expensive to reach a position. An ArrayList is the reverse.

How ArrayList grows — and the number everybody half-remembers

The popular answer is "default capacity 10". Running it on JDK 17 shows the fuller picture: a new ArrayList<>() starts with a zero-length array, the capacity of 10 appears on the first add(), and when 10 fills up the array grows by half again — to 15, not 20. Growth means allocating a new array and copying, which is why new ArrayList<>(50_000) is worth writing when you already know the size.

Vector, and why it is not the answer

Vector is an ArrayList whose every method is synchronized. It is legacy — Java 1.0, predating the framework. Two reasons not to use it: you pay for locking on every single call even in single-threaded code, and that per-method lock does not make compound operations safe anyway. if (!v.contains(x)) v.add(x); is still a race, because another thread can slip between the two calls. Real concurrent collections are Ch6's subject.

Standard definition: ArrayList is a resizable-array implementation of the List interface offering constant-time positional access, while LinkedList is a doubly-linked-list implementation offering constant-time insertion and removal at a known position but linear-time positional access.

When to use it: default to ArrayList, and mean it — reading by index, iterating, and appending at the end are what almost all code does, and ArrayList is best or tied at all three. Reach for LinkedList only when the work is genuinely at the ends or through an iterator, such as a queue you add to at one end and drain from the other.

When NOT to use it: do not choose LinkedList because you read that "insertion is O(1)" — if you reach the position with an index, you paid a walk to get there and the constant is worse than ArrayList's shift for anything but very large lists. Trade-off: ArrayList costs occasional resize-and-copy plus shifting on middle inserts; LinkedList costs an object with two pointers for every element and a walk for every indexed access. In ordinary business code ArrayList wins on both memory and speed, which is why it is the default.

import java.util.*;

public class T2 {
    public static void main(String[] args) {
        int N = 100_000;
        List<Integer> arrayList  = new ArrayList<>();
        List<Integer> linkedList = new LinkedList<>();
        for (int i = 0; i < N; i++) { arrayList.add(i); linkedList.add(i); }

        System.out.println("ArrayList  is RandomAccess? " + (arrayList  instanceof RandomAccess));
        System.out.println("LinkedList is RandomAccess? " + (linkedList instanceof RandomAccess));

        long t0 = System.nanoTime();
        long sum1 = 0;
        for (int i = 0; i < N; i++) sum1 += arrayList.get(i);      // seedha index
        long t1 = System.nanoTime();
        long sum2 = 0;
        for (int i = 0; i < N; i++) sum2 += linkedList.get(i);     // har baar chal kar dhoondhna
        long t2 = System.nanoTime();

        System.out.println("get(i) x " + N + "  ArrayList  : " + (t1 - t0) / 1_000_000 + " ms");
        System.out.println("get(i) x " + N + "  LinkedList : " + (t2 - t1) / 1_000_000 + " ms");

        long t3 = System.nanoTime();
        for (int i = 0; i < 20_000; i++) arrayList.add(0, i);      // shuruaat me insert
        long t4 = System.nanoTime();
        for (int i = 0; i < 20_000; i++) linkedList.add(0, i);
        long t5 = System.nanoTime();
        System.out.println("add(0, x) x 20000  ArrayList  : " + (t4 - t3) / 1_000_000 + " ms");
        System.out.println("add(0, x) x 20000  LinkedList : " + (t5 - t4) / 1_000_000 + " ms");
    }
}

Project: Student Gradebook

A class teacher keeps one register. Each student has a row, and the row grows across the term — unit test, mid-term, practical, final. At the end she needs three things: each student's average, a rank list, and the names above the distinction cutoff.

That single sentence contains four decisions from this chapter, and this project is where you make all of them yourself.

What you are building

A Gradebook class with four operations:

  • addMark(student, mark) — add one mark for a student who may or may not exist yet
  • average(student) — their average, and no crash for a student who does not exist
  • rankList() — every student ordered by average, highest first, ties broken by name
  • toppers(cutoff) — the distinct names at or above a cutoff

Step 1 — Pick the shape before writing a line

Run the three questions.

Do duplicates mean anything? Yes — Aarav really did score 76 twice, and both marks count towards his average. So marks are a List, not a Set.

Do I look anything up by a key? Constantly, by student name. So the container is a Map.

What order do I need? The raw register is printed for a human, so insertion orderLinkedHashMap.

That gives Map<String, List<Integer>>. One key, many values, is the single most common Map shape in real code.

Step 2 — computeIfAbsent, and the null check you never write

The obvious version has a bug on the first mark of every student:

marks.get(student).add(mark);            // NullPointerException the first time

The naive fix is four lines of null checking. The idiom is one:

marks.computeIfAbsent(student, s -> new ArrayList<>()).add(mark);

If the key is missing, the lambda builds an empty list, stores it, and hands it back. If the key is present, the existing list is handed back untouched. Either way you get a list and you add to it.

Remember this shape. Map<K, List<V>> with computeIfAbsent is how grouping is written in Java, and it turns up in every second interview task.

Step 3 — Return empty, not null

List<Integer> list = marks.getOrDefault(student, List.of());
if (list.isEmpty()) return 0;

average("Neha") printed 0.0 instead of throwing, because getOrDefault supplied an empty list for an unknown student. A method that returns an empty collection instead of null removes a null check from every caller — and callers forget null checks.

Step 4 — The rank list: a Map cannot be sorted by value

This is the step people get wrong. There is no sorted-by-value Map, and TreeMap sorts by key. Copy the rows out and sort the List:

List<Map.Entry<String, Double>> rows = new ArrayList<>();
for (String s : marks.keySet()) rows.add(Map.entry(s, average(s)));
rows.sort(Map.Entry.<String, Double>comparingByValue().reversed()
                   .thenComparing(Map.Entry::getKey));

Read the chain left to right: by average, reversed so the highest leads, ties broken by name. In the run, Aarav and Ishaan both average 76.0 and Aarav appears first — the tie-breaker doing its job.

⚠️ Do not be tempted by TreeMap<Double, String> keyed on the average. Two students share 76.0, and a Map key is unique, so one of them would silently vanish. That mistake produces a rank list that is short by a student and shows no error at all.

Step 5 — Toppers as a Set

A name should appear once, so it is a Set, and TreeSet gives alphabetical output with no sort call. The printed [Aarav, Diya, Ishaan] is sorted purely because of the container choice.

Try it yourself

  1. Add subjects: change the value to Map<String, Map<String, List<Integer>>> so each student has marks per subject, and produce a per-subject topper.
  2. Add dropLowest(student) — remove the single lowest mark before averaging. Use Collections.min and remember the removal must not disturb an ongoing iteration.
  3. Print the rank list with proper ranks for ties: two students on 76.0 are both rank 2, and the next student is rank 4.
  4. Change LinkedHashMap to HashMap and run it again. The averages and the rank list are identical; only the raw dump order changes. Understanding why that is safe is the point of the exercise.
import java.util.*;

public class Gradebook {
    private final Map<String, List<Integer>> marks = new LinkedHashMap<>();

    void addMark(String student, int mark) {
        marks.computeIfAbsent(student, s -> new ArrayList<>()).add(mark);
    }
    double average(String student) {
        List<Integer> list = marks.getOrDefault(student, List.of());
        if (list.isEmpty()) return 0;
        int total = 0;
        for (int m : list) total += m;
        return Math.round(total * 100.0 / list.size()) / 100.0;
    }
    List<Map.Entry<String, Double>> rankList() {
        List<Map.Entry<String, Double>> rows = new ArrayList<>();
        for (String s : marks.keySet()) rows.add(Map.entry(s, average(s)));
        rows.sort(Map.Entry.<String, Double>comparingByValue().reversed()
                           .thenComparing(Map.Entry::getKey));
        return rows;
    }
    Set<String> toppers(double cutoff) {
        Set<String> out = new TreeSet<>();
        for (String s : marks.keySet()) if (average(s) >= cutoff) out.add(s);
        return out;
    }

    public static void main(String[] args) {
        Gradebook gb = new Gradebook();
        gb.addMark("Ishaan", 72); gb.addMark("Ishaan", 80);
        gb.addMark("Diya", 91);   gb.addMark("Diya", 89);
        gb.addMark("Aarav", 76);  gb.addMark("Aarav", 76);
        gb.addMark("Kabir", 65);

        System.out.println("raw marks     : " + gb.marks);
        System.out.println("Diya average  : " + gb.average("Diya"));
        System.out.println("Neha average  : " + gb.average("Neha"));   // koi crash nahi

        System.out.println("--- rank list ---");
        int rank = 1;
        for (Map.Entry<String, Double> row : gb.rankList()) {
            System.out.println("  " + rank++ + ". " + row.getKey() + "  " + row.getValue());
        }
        System.out.println("toppers(>=76) : " + gb.toppers(76));
    }
}

Collections and Genericsinterview questions & answers

10 sample questions below — 211+ in the full bank inside.

You are given a List<String> of usernames with repeats and must return each name once, in the order it first appeared. Which collection?

LinkedHashSet, then wrap it back into a List: new ArrayList<>(new LinkedHashSet<>(names)). The deciding property is that LinkedHashSet is the only Set that both rejects duplicates and iterates in insertion order. The runner-up is HashSet, which de-duplicates just as well but returns an arbitrary order — on my run [login, search, login, checkout, search] came back as [search, login, checkout], with the first-seen order gone. TreeSet also de-duplicates but reorders alphabetically and throws NullPointerException on a null name.

In simple terms: This is the single most common small coding task in a collections round, and the interviewer is usually watching whether you notice the order clause at all — most candidates reach for HashSet by reflex. LinkedHashSet buys the order with two extra references per entry, which is a trivially cheap fix for silently wrong output.

What does map.put() return, and why does that matter?

It returns the value that was previously stored under that key, or null if the key is new. So put("Aarav", 91) prints null and a following put("Aarav", 95) prints 91. That means you get "was this key already present" for free, without a separate containsKey or get call.

In simple terms: It is the same idea as Set.add returning a boolean — the collection already knows whether it changed, so it tells you rather than making you ask twice. Reading it in one call also avoids the gap between a check and the write that follows it.

An application logs events and must preserve the order in which they happened. Which collection would you use?

ArrayList<Event>. The deciding property is that a List keeps insertion order and allows repeats, and a repeated event is real data — the same user logging in twice is two events, not one. The runner-up is LinkedHashSet, and it is only correct if each distinct event must appear once; it silently drops the second login. Plain HashSet is wrong for both reasons — it drops repeats and gives no order at all.

In simple terms: For [login, search, login, checkout, search] I ran all three: LinkedHashSet gave [login, search, checkout] and HashSet gave [search, login, checkout]. The LinkedHashSet result is order-correct but has lost two events; the HashSet result has lost the events and the order. Only the ArrayList still holds the actual log.

What is the difference between List, Set and Map?

A List is ordered and allows duplicates, and you can reach elements by index. A Set does not allow duplicates and promises no order unless you pick LinkedHashSet or TreeSet. A Map stores key-value pairs with unique keys and is designed for lookup by key rather than scanning.

In simple terms: Pick by the question you are asking. Attendance for one lecture is a Set — a roll number is present or not. Every sale of the day is a List — two identical sales are two sales. Roll number to marks is a Map.

What is the difference between HashSet and HashMap?

HashMap stores key-value pairs and is used for lookup; HashSet stores single values and answers only membership. Internally HashSet is a HashMap whose values are all one shared dummy object, so their performance and their equals/hashCode requirements are identical.

In simple terms: The decision is whether you need data attached to each unique thing. Unique visitor ids is a Set; visits per id is a Map.

Name four useful methods on the Collections utility class and what they do.

Collections.sort(list) sorts a list in place; Collections.max(collection) and min return the largest and smallest by natural order; Collections.reverse(list) reverses it; Collections.unmodifiableList(list) returns a read-only view; Collections.emptyList() gives an immutable empty list to return instead of null.

In simple terms: They are all static, and several of them return views rather than copies — knowing which is which is what the immutability topic is about.

Can you add null to a HashSet or a TreeSet?

A HashSet accepts one null without complaint, because hashing null is defined to land in bucket zero. A TreeSet throws NullPointerException, because placing an element in a sorted tree means comparing it, and null cannot be compared.

In simple terms: Only one null is ever possible in any Set, since a second one would be a duplicate. The same split appears again between HashMap, which allows a null key, and TreeMap, which does not.

Employee ids must be unique and an employee must be searchable by id quickly. Which collection would you use, and why?

HashMap<String, Employee>, with the id as the key. The deciding property is that a Map key is unique by definition and get(id) is one average O(1) step, so both requirements are answered by the same choice. The runner-up is an ArrayList<Employee> plus a loop, and it costs a full O(n) scan on every lookup — at ten thousand employees that is ten thousand comparisons to answer a question a HashMap answers with one hash.

In simple terms: Think of an office register sorted by nothing versus an ID-card reader. The list makes you read every row; the reader jumps straight to the person. The word searchable in a requirement is almost always the word Map. If you also wanted to detect a duplicate id being added, note that put returns the previous value, so a non-null return tells you the id was already taken.

Java job descriptions list Collections as a core skill. When you are given a requirement, how do you decide which collection to use?

I ask three questions in order. First, do duplicates mean anything? If two identical entries are two real facts I need a List, otherwise a Set. Second, do I ever look something up by a key? If I would write a loop to find a record by id, that loop is a Map. Third, what order do I need — none gives HashMap or HashSet, arrival order gives ArrayList or LinkedHashSet or LinkedHashMap, sorted order gives TreeMap or TreeSet, most-important-first gives PriorityQueue, and first-in-first-out gives ArrayDeque. The default is HashMap and ArrayList, because they are the cheapest, and I only move up when a requirement actually asks for order.

In simple terms: The JD asks for Collections because production code is mostly collection code. Interviewers rarely ask you to define ArrayList; they describe a situation and watch whether you can name one class and defend it. Having a fixed order of questions means you never freeze, and it stops the common mistake of picking TreeMap because sorted sounds safer.

What is the Java Collection Framework and why was it added?

It is a unified set of interfaces and implementations in java.util for storing groups of objects — List, Set, Queue and Map, plus classes like ArrayList and HashMap. It was added so that ready-made, resizable data structures and their algorithms (sorting, searching, shuffling) come with the language, and so that different collection types can be swapped through one common API.

In simple terms: Before it, everyone wrote their own growable array and their own search loop, and none of them fit together. Now a method that accepts List works with ArrayList, LinkedList or anything else that implements it, which is the real gain.

201+ more Collections and Generics questions inside

Create a free account to read the full question bank, learn every topic, and practise with an AI mock interview.

Unlock all questions — free

Ready to practise Collections and Generics?

Unlock every topic free, then face an AI interviewer that asks follow-ups and grades your answers.