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
ArrayList uses a backing array, so get(i) is O(1) but inserting or removing near the front shifts items, O(n). LinkedList uses nodes, so adding and removing at the ends is O(1) but get(i) walks the chain, O(n). LinkedList also uses much more memory per item.
Only for the relinking part, or at the two ends. If I insert by index, the list must first walk to that position, which is O(n). It is truly O(1) only when I already stand at the spot with an iterator, for example ListIterator.add.
The LinkedList. Each item needs its own node object with a header and two links, roughly 24 bytes on a typical 64-bit JVM, while an ArrayList needs about 4 bytes per array slot. Even the spare capacity of an ArrayList usually costs less.
A modifiable list, like ArrayList, allows every change. A fixed-size list, like the one from Arrays.asList, allows set but not add or remove. An unmodifiable list, from List.of, List.copyOf or Collections.unmodifiableList, allows no changes; any attempt throws UnsupportedOperationException.
List.copyOf(source) return when source is already an immutable list?It can return the same list object and skip the copy, because an immutable list can never change, so sharing it is safe. If the source is a normal ArrayList, it builds a new read-only list. It still throws NullPointerException if any item is null.
Nobody holding it can add, remove or replace an item, so one part of the program cannot surprise another. It needs no locks when different threads only read it, because its content never changes after it is built. The items must be immutable too, such as strings or records, or their fields could still be changed.
Lesson: Fixed and unmodifiable listsList.of list but need to add to it. What do you do?I make my own modifiable copy with new ArrayList<>(thatList) and change the copy. The original stays untouched, which is usually what its owner wanted. For an array I use new ArrayList<>(Arrays.asList(array)).
I would write students.removeIf(s -> s.getMarks() < 35). Calling list.remove inside a for-each loop throws ConcurrentModificationException, because the hidden iterator sees the list change. If I also need to log each removal, I use an explicit Iterator and it.remove().
new ArrayList<>(list), List.copyOf(list) and Collections.unmodifiableList(list)?new ArrayList<> is a separate copy anyone can change. List.copyOf is a separate, read-only snapshot. unmodifiableList is a read-only view of the same list, so it shows the owner's later changes. All three are shallow: the objects inside are shared.
OrderService holds a list of orders. How do you let other classes read the orders but not change them?I keep the list private final, add orders only through service methods that validate them, and return List.copyOf(orders) from the getter, or an unmodifiable view if callers need live data. If Order is mutable, I would make it immutable, for example a record, so callers cannot change an order through the list either.
compareTo return?A negative number if this should come before other, zero if they have the same place, and a positive number if this comes after. Only the sign matters, not the size of the number.
Collections.sort fail at compile time for a non-comparable class, but Arrays.sort fails only at run time?Collections.sort is declared as <T extends Comparable<? super T>> void sort(List<T> list), so the compiler checks the bound. Arrays.sort(Object[] a) accepts any object array, so it compiles and then fails with ClassCastException when it casts an element to Comparable.
return this.marks - other.marks;?Subtraction can overflow for large or negative numbers and then return the wrong sign. Integer.compare(this.marks, other.marks) is always correct and just as short. The module's practice lesson shows the overflow happening.
Comparable<Student> instead of the raw Comparable?With the type argument, the method is compareTo(Student other), so I can read other.marks without a cast, and the compiler stops anyone comparing a Student with a String. The raw version takes Object and pushes errors to run time.
Comparator instead of Comparable?When I need a second order, like by name on one screen and by marks on another, or when I cannot edit the class, like String or a library class. I also use it when the class has no obvious natural order.
Comparator.comparing(s -> s.getMarks()).reversed() not compile, when the same thing without .reversed() does?Java infers the lambda's parameter type from the target type. Without .reversed(), the target is sort's parameter, so s is a Student. With .reversed(), the call is just the start of a chain, so Java has no target and uses Object. Writing (Student s) -> or storing the comparator in a typed variable fixes it.
thenComparing do?It adds a second key that is used only when the first comparator returns 0. For example comparing(Player::getTeam).thenComparing(Player::getName) groups players by team and sorts names inside each team.
comparing and comparingInt?They give the same order. comparing boxes each int key into an Integer object before comparing; comparingInt compares the int values directly, so it is a little faster and uses less memory on large lists.
A stable sort keeps equal elements in their original order. List.sort, Collections.sort and Arrays.sort for objects are stable; they use TimSort, which is based on merge sort. For primitive arrays stability does not matter, because equal numbers cannot be told apart.
NullPointerException for some records. What is happening and how do you fix it?Either an element or a sort key is null, and the comparator calls compareTo on it. I wrap the key comparator with Comparator.nullsFirst(...) or nullsLast(...), for example comparing(s -> s.getCity(), Comparator.nullsLast(Comparator.naturalOrder())). I also check why the data has nulls at all.
I sort, then walk the list with an index. If the current item has the same score as the one before, it keeps the previous rank; otherwise its rank is index + 1. That gives 1, 2, 2, 4, the usual competition ranking.
Lesson: Sorting real datacomparingInt instead of comparing for an int field?comparing boxes every int key into an Integer before comparing it, which creates many small objects on big lists. comparingInt compares the int values directly. The order is the same.
double values in a comparator?With Double.compare(a, b) or Comparator.comparingDouble(...). Casting the difference to int turns any gap smaller than 1 into 0, so 8.4 and 8.9 look equal.
Collections.sort(list), list.sort(null) and Arrays.sort(array)?The first two sort a List in natural order; Collections.sort simply calls list.sort. Arrays.sort sorts an array. All of them sort in place and return void, and all of them accept a comparator for a custom order.
Copy the list with new ArrayList<>(students), sort the copy with a comparator like marks high to low then name, and read the first 3 items, checking that the list has at least 3.
add return on a HashSet, and how is that useful?It returns true if the value was new and was added, and false if it was already there. That lets me detect duplicates in one step: if (!seen.add(id)) means I have seen this id before, for example a repeated payment request.
No. The order depends on hash codes and the internal table, and it can change as the set grows. If I need insertion order I use LinkedHashSet; if I need sorted order I use TreeSet or copy the set into a list and sort it.
null? What about a set from Set.of?A HashSet can hold one null. Set.of does not allow null at all and throws NullPointerException. It also throws IllegalArgumentException for duplicate values, and the set cannot be changed later.
Order gets its id only after it is saved. Would you put that id in hashCode if orders are already in a HashSet?No, because the id changes from unset to a real value, so the hash code changes while the order is inside the set and the order is lost in its old bucket. I would use a field that is set at creation and never changes, such as an order number made up front. If no such field exists, I add the order to the set only after it is saved.
Lesson: Sets of your own objectsYes, that is a collision and it is allowed. Both objects go into the same bucket, and equals tells them apart. Too many collisions only make the set slower; a hashCode that returns a constant turns the set into a slow list.
A record gets equals and hashCode generated from all its components, so they always agree. Its fields are final, so an element cannot change after it is added. That removes both classic set bugs.
hashCode() { return 42; } correct?It follows the contract, so the set still gives correct answers. But every object lands in the same bucket, so add and contains become O(n) instead of O(1). It is correct but a performance bug.
It uses compareTo, or the comparator passed to its constructor. If the comparison returns 0, the new element is treated as a duplicate and not added. It never calls equals or hashCode, so a comparator that compares only marks will drop students with equal marks.
null or an object that is not Comparable to a TreeSet?null throws NullPointerException, because the set must compare it. An object whose class is not Comparable, with no comparator given, throws ClassCastException on the first add. Both are run-time errors; the code compiles.
When I need the duplicates removed but the original order kept, such as a search history or a list of form entries shown back to the user. The cost is a little more memory for the links.
Lesson: LinkedHashSet and TreeSetYes, EnumSet is a set made only for enum values, like EnumSet.of(Day.SAT, Day.SUN). It is very fast and compact and returns values in the enum's declared order, so it is the best choice for a set of enum constants.
retainAll and addAll return?A boolean that says whether the set changed. addAll returns false if every value was already there, and retainAll returns false if nothing had to be removed.
That is the symmetric difference. I build the union of copies, build the intersection, and remove the intersection from the union. Java has no single method for it.
Lesson: Union, intersection and differenceretainAll on a set created with Set.of?It throws UnsupportedOperationException, because Set.of sets cannot change. I copy it into a HashSet or TreeSet and call retainAll on the copy.
It keeps an array of buckets. For each element it calls hashCode() to choose a bucket and equals() to check the elements already in that bucket. In fact a HashSet is built on a HashMap: each element is stored as a key, which the Maps module explains in detail.
Not with set.remove inside a for-each, which throws ConcurrentModificationException. I use set.removeIf(condition), or an explicit Iterator and it.remove().
It means compareTo returns 0 exactly when equals returns true. If they disagree, a TreeSet and a HashSet holding the same objects can have different sizes, because one uses comparison and the other uses equals. For example, comparing only by marks makes a TreeSet drop students that a HashSet keeps.
I put all numbers in a HashSet. A number starts a run only if x - 1 is not in the set; from such a start I count x + 1, x + 2 and so on while they are present. Each number is visited at most twice, so the whole thing is O(n).
A List keeps values in positions 0, 1, 2… and you look them up by index. A Map keeps pairs and you look up a value by its key, like a PNR or roll number. A list may hold duplicates; a map's keys are unique.
get with a key that is not in the map?It returns null; it does not throw. The danger comes after: if you assign that null to an int, unboxing throws a NullPointerException. I use getOrDefault or check containsKey first.
With putIfAbsent(key, value). It adds the entry only for a new key and returns null; if the key exists it changes nothing and returns the current value. This is useful when a new booking must never overwrite an old one.
No. Entries are placed by the keys' hash codes, and the order can even change when the map grows. If order matters I use LinkedHashMap for insertion order or TreeMap for sorted keys.
entrySet() usually better than keySet() plus get()?With keySet() I find each key and then look it up again with get, so every entry costs two steps. entrySet() hands me the key and value together in one step. The code is also shorter and clearer.
The hidden iterator detects that the map changed and throws ConcurrentModificationException. I use map.values().removeIf(…) or map.entrySet().removeIf(…), or an explicit Iterator and its remove() method.
keySet()?No. keySet().add("X") throws UnsupportedOperationException, because a key alone has no value to store. You add entries with put; the views only support reading and removing.
I split the sentence into words and use a Map<String, Integer>. For each word I call count.merge(word, 1, (a, b) -> a + b), or put(word, getOrDefault(word, 0) + 1). It is one pass, so it takes O(n) time for n words.
computeIfAbsent and putIfAbsent?putIfAbsent(k, v) takes a ready value and returns the old value, which is null when it adds. computeIfAbsent(k, f) makes the value only when needed and returns the value that is now in the map. For grouping, computeIfAbsent is the right one.
First I build a frequency map with merge. Then I loop over entrySet() and keep the entry with the biggest value. I also decide a tie rule, for example the first one in sorted order.
In the first pass I count every character in a map. In the second pass I walk the string from the start and return the first character whose count is 1. Both passes are O(n).
Lesson: Counting and grouping with mapsgetOrDefault and computeIfAbsent?getOrDefault(k, d) only reads: it returns d for a missing key and leaves the map unchanged. computeIfAbsent(k, f) creates the value with f, stores it in the map and returns it. I use the first for lookups and the second to build values such as lists.
compute or merge returns null?The entry is removed from the map. This is a neat way to delete a product when its stock reaches 0 in the same call that updates it.
Lesson: Modern Map methods: getOrDefault, merge, computeIfAbsentcomputeIfAbsent not change the same map?The map is in the middle of the call, so a change from inside leaves it in an unclear state. HashMap detects this since Java 9 and throws a ConcurrentModificationException. A recursive memoised Fibonacci is the classic example: read with get, calculate, then put.
visits.merge(page, 1, (a, b) -> a + b);. For the first visit it stores 1, and for later visits it adds 1 to the old count. It never throws a NullPointerException.
They land in the same bucket, which is a collision. The bucket keeps both entries in a chain, and equals decides which one matches. Since Java 8, a long chain becomes a small tree so lookups stay fast.
equals and hashCode for a key class?hashCode decides the bucket and equals decides the match. If I override only equals, two equal keys usually get different hash codes and land in different buckets, so get returns null. If I override neither, identity is used and no new object ever matches.
Each lesson has quizzes, practice and more interview questions on the same topic.
Open the first lesson