Loading...
Loading...
Java interview guide
List, Set, Map, Queue, generics and how the main collections work inside. Every answer is short enough to say aloud, and every question links to the lesson that teaches it.
60 questionsLast updated
Object. So why do we need generics at all?An Object holder works, but every read needs a cast, and the Java compiler cannot check that cast. A wrong type compiles and crashes later with a ClassCastException, often far from the real bug. Generics keep the one-class flexibility and add compile-time checks.
List<Order> findOrders() help the next developer who reads your code?The return type itself says what is inside, so the reader does not need to open the method or guess. They can write for (Order o : findOrders()) with no cast, and the compiler stops them from adding a Customer by mistake. With a raw List, the same facts would sit in a comment that nothing checks.
A raw type is a generic class used without a type argument, like ArrayList instead of ArrayList<String>. The Java compiler switches off its checks and gives only an "unchecked" warning. Java allows it so that code written before Java 5 still compiles, but new code should never use it.
A generic class is a class that declares one or more type parameters in angle brackets after its name, like class Box<T>. Each use fills them in, so one class gives Box<String>, Box<Integer> and more, all checked by the Java compiler.
Box<int>?A type argument must be a reference type, and int is a primitive. You use the wrapper class instead, Box<Integer>, and autoboxing converts between int and Integer for you.
null Integer?You get a NullPointerException, because unboxing calls intValue() on the object, and there is no object. It compiles fine, so it only shows up at run time; check for null first.
Source<T>?Two ways. It can fix the type, class TicketMachine implements Source<Integer>, and then write public Integer next(). Or it can stay generic, class Repeater<T> implements Source<T>, and let its own users choose T.
After the modifiers and before the return type: public static <T> T firstOf(T[] items). It must come before the return type because the return type itself may use T, and a name must be declared before it is used.
T?The class's T is chosen per object: one Box is a Box<String>, another a Box<Integer>. A static method belongs to the class and runs without any object, so there is no T to use. It must declare its own, like static <T> T pick(T a, T b).
When only one operation needs to be generic and there is no state to keep, such as swap, firstOf or indexOf. A generic class is for objects that hold values of type T, like Box<T>; a generic method is for actions on values of any type.
A bounded type parameter is a type parameter with a limit, written <T extends Bound>. Only the bound or its subtypes may be used for T, and inside the code T can use every method of the bound, such as doubleValue() for Number.
extends even when the bound is an interface?In a bound, extends simply means "is this type or a subtype of it", whether that type is a class or an interface. Java uses one keyword for both, so <T extends Comparable<T>> is correct and <T implements Comparable<T>> is a syntax error.
Join them with &: <T extends Number & Comparable<T>>. There can be at most one class, and it must come first, followed by any number of interfaces; otherwise the Java compiler says "interface expected here".
The Java compiler refuses the call at compile time. For example, passing a String[] to <T extends Number> double total(T[] a) gives "inference variable T has incompatible bounds", so the wrong type never reaches the code.
ArrayList<Integer> not a subtype of ArrayList<Number>, when Integer[] is a Number[]?Generics are invariant on purpose. If the list conversion were allowed, you could add a Double through the ArrayList<Number> reference into a list of Integers. Arrays allow it, so the same mistake compiles and fails at run time with ArrayStoreException.
ArrayList<? extends Number>?Because the real list could be an ArrayList<Integer> or an ArrayList<Double>, and the Java compiler does not know which. Any value you add could be the wrong kind, so every add is refused. Reading is safe, since every element is some Number.
It is a short way to remember this lesson: Producer Extends, Consumer Super. A list you only read from (it produces values) takes ? extends T; a list you only add to (it consumes values) takes ? super T, as in Java's Collections.copy.
<T extends Number> and ? extends Number?Both mean "Number or a subtype". <T extends Number> gives the type a name, so you can use T again, for example to return a T or to require two parameters of the same type. ? extends Number is anonymous and is enough when the type is used only once.
For backward compatibility. Generics arrived in Java 5, and erasure let new generic code and old raw code share libraries and run on the same JVM without changing old class files.
Lesson: Type erasure and generic limitsString s = box.get(); work without a cast?The compiler adds the cast for you: what runs is String s = (String) box.get();. Because the compiler already checked every set, that cast cannot fail unless raw types were used.
obj instanceof Box<String> not compile?The JVM only knows the object is a Box; the <String> was erased, so it cannot be checked. Use obj instanceof Box<?>, which asks only what the JVM can answer.
print(Box<String>) and print(Box<Integer>)?No. After erasure both are print(Box), so the Java compiler reports "name clash ... have the same erasure". Use different method names instead.
ArrayList, ArrayList<Object> and ArrayList<?>?A raw ArrayList switches the checks off and only warns. ArrayList<Object> is a checked list that may hold any object, but it does not accept an ArrayList<String>. ArrayList<?> accepts a list of any element type, lets you read Objects and blocks every add except null.
I use a bounded type parameter like <T extends Comparable<T>> when I need to name the type, for example to return a T or to make two parameters the same type. I use a wildcard like ? extends Number when the type appears once and I only read or only write.
find(catalog, "Keyboard")?I would write a small generic class Result<T> with a private constructor, a T value and an error string, and two static factory methods, ok(T value) and fail(String error). The method returns Result<Product>, and the caller checks isOk() before calling get(), with no casts and no null surprises.
It is the set of interfaces and classes in java.util for storing and working with groups of objects, such as List, Set, Map, ArrayList and HashMap. They share method names, so once you learn one, the others feel familiar.
length of an array and size() of an ArrayList?length is the room the array was created with, even if some slots are empty. size() is the number of items really in the list, and it changes as you add and remove.
No. An array is still the right choice when the size is fixed and known, like 12 months or a 3x3 board, and for large amounts of primitive numbers, because it stores them without wrapper objects. For data that grows or shrinks, use a collection.
Lesson: Why collections, not arrays?At the top is Iterable. Collection extends it, and List, Set and Queue extend Collection; Deque extends Queue. Map is separate: it does not extend Collection. Under these interfaces are the classes, like ArrayList, HashSet, ArrayDeque and HashMap.
Map not a Collection?A Collection holds single elements, with methods like add(element). A map holds key-value pairs and needs put(key, value) and get(key), so the Collection methods do not fit. A map can still give collection views: keySet(), values() and entrySet().
new List<>()? Why not?No. List is an interface; it only describes what a list can do and has no code to store items. You must create a class that implements it, such as new ArrayList<>().
Iterable?Iterable is what makes the for-each loop work. Every list, set and queue is Iterable, so for (String s : collection) works on all of them. A map is not, so you loop over one of its views instead.
They describe how the time grows as the data grows. O(1) stays about the same, O(log n) grows very slowly (about 20 steps for 10 lakh items), and O(n) grows with the size, because every item may be checked.
Lesson: Choosing the right collectionHashSet, LinkedHashSet or TreeSet?HashSet when I only need uniqueness and speed, and order does not matter. LinkedHashSet when I also want insertion order. TreeSet when I want the items sorted, and I accept O(log n) operations.
A Map<String, Student>, usually a HashMap, with the roll number as the key. Each lookup is one get call, O(1) on average, instead of scanning a list every time.
List<int>? What really happens with List<Integer>?Generics work only with classes, and int is a primitive. With List<Integer>, autoboxing wraps each int into an Integer object when you add it, and unboxing turns it back when you read it. It is convenient, but each number becomes a small object.
for (String s : map) compile, and how do you loop over a map?A Map does not extend Iterable, so the for-each loop cannot use it directly. I loop over a view: map.keySet() for keys, map.values() for values, or map.entrySet() for both.
Collection have a get(int index) method?Because not every collection has positions. A Set or a Queue has no index 0 or 1, so get(int) lives in List, where positions make sense.
A LinkedHashMap<String, Participant> keyed by phone number does all three: keys are unique, it keeps insertion order, and get(phone) is O(1) on average. Without it, I would combine a List for order with a HashMap for lookup.
List<String> names = new ArrayList<>() instead of ArrayList<String> names = new ArrayList<>()?It is programming to the interface. The rest of the code depends only on List, so I can later switch to another List class by changing one line. It also keeps method parameters general: a method taking List<String> accepts any kind of list.
add, set and the two remove methods return?add(e) returns true, and add(i, e) returns nothing. set returns the old item it replaced. remove(int) returns the removed item, and remove(Object) returns true if it found and removed an equal item, otherwise false.
null?Yes to both. It keeps every item I add, in order, even if it is equal to another one, and it accepts null. But if I read a null from a List<Integer> into an int, unboxing throws a NullPointerException.
An index loop when I need the position or want to set items; a for-each loop for plain reading; an Iterator when I must remove items during the loop; and forEach with a lambda for a short read-only action. Most of my loops are for-each.
The compiler turns it into an iterator loop: it calls iterator(), then hasNext() and next() for every item, and puts each item in the loop variable. I get the items in order, but not the index, and the variable is only a copy of each reference or value.
When I need the position: to number items, to compare an item with its neighbour, to go backwards, or to change items with set(i, value). For plain reading, for-each is shorter and has no index mistakes.
for (int p : prices) { p = p + 50; } not change the list?p is a separate local variable that holds a copy of each value. Changing it never touches the list. I would use prices.replaceAll(p -> p + 50), or an index loop with prices.set(i, prices.get(i) + 50).
ConcurrentModificationException when you remove from a list inside a for-each loop?The list keeps a change count and the hidden iterator remembers it. When I call list.remove() directly, the count changes, and on the next next() the iterator sees the mismatch and throws. This fail-fast check protects me from reading a list in a broken state.
Not necessarily. It mostly happens in single-threaded code, when the same code changes a list it is looping over. The name only means "the list was changed while being iterated".
Lesson: Iterator and safe removaliterator.remove() and list.remove(x) inside a loop?iterator.remove() removes the item that next() just returned and keeps the iterator in step with the list, so the loop goes on safely. list.remove(x) changes the list behind the iterator's back, so the next next() throws ConcurrentModificationException (or the loop quietly skips an item).
contains and indexOf?contains only tells me whether an equal item exists, as true or false. indexOf also tells me where the first one is, or returns -1. In fact contains(x) is just indexOf(x) >= 0 inside ArrayList.
Collections.sort(list) and list.sort(null)?They give the same result. list.sort is the newer instance method from Java 8, and Collections.sort(list) simply calls it. Passing null means natural order; passing a Comparator gives a custom order.
It checks the middle item and throws away the half where the key cannot be, again and again, so it needs about log2(n) steps: around 20 for 10 lakh items. Throwing away a half is only safe when the items are in order. On an unsorted list it can say "not found" for an item that is there.
Lesson: Search and sort a listNo. Sorting takes more work (O(n log n)) than a single linear search (O(n)), so for one search I just call contains. Sorting first pays off only when I search the same list many times.
list.contains(obj) return false even though an object with the same data is in the list?contains uses equals, and if the class does not override it, Object.equals only checks whether it is the same object, like ==. I fix it by overriding equals on the identifying field, for example the roll number, or by using a record.
If the object is mutable, I find it, usually with a loop on its id, and call a setter. The list holds the same object, so nothing else is needed. If it is immutable, like a record, I create a new object and put it in with list.set(index, newObject).
List.copyOf(list) and new ArrayList<>(list)?Both are separate copies, so my internal list is safe either way. List.copyOf is read-only, and any change throws UnsupportedOperationException. new ArrayList<> gives the caller a list they can change freely, which suits callers who want to sort or filter their own copy.
It keeps the items in an ordinary Object[] array plus a size counter. get and set read the array slot directly. When the array is full, add creates a bigger array, about 1.5 times the size, copies all items over and continues there.
add is O(1)?It is amortized O(1). A growth is O(n), but after growing by half there are many cheap adds before the next growth. Averaged over all adds, the copying works out to about two copies per item, which is a constant.
Lesson: How ArrayList works insidenew ArrayList<>(100) create a list with 100 null items?No. It only reserves a backing array of 100 slots; the size is still 0. get(0) or set(0, x) would throw IndexOutOfBoundsException until I add items.
get(i) O(1) for an ArrayList?Array slots are all the same size and sit next to each other in memory, so the address of slot i is computed directly from the start address and i. No other items are visited, whatever the size of the list.
Each lesson has quizzes, practice and more interview questions on the same topic.
Open the first lesson