Loading...
Loading...
Java interview guide
Questions from the interview-preparation section: core Java, OOP, collections, output prediction and backend rounds. Every answer is short enough to say aloud, and every question links to the lesson that teaches it.
61 questionsLast updated
A TreeMap<String, List<String>> keyed by block name, filled with computeIfAbsent(block, k -> new ArrayList<>()).add(student). The TreeMap keeps the blocks sorted, so printing them is already A to Z.
If the field is used in hashCode, the object now belongs in another bucket. contains and remove look in the new bucket and fail, while the object stays stuck in the old one.
Follow-up: How do you avoid that problem? Use immutable elements such as String, Integer or records, so the hash cannot change after the object is added.
Common mistake: Saying the set throws an exception when a field changes. Nothing is thrown: the object just becomes unreachable by contains and remove.
Yes, that is a normal collision. Both are stored in the same bucket and equals keeps them apart. It is only a speed concern if it happens a lot.
Because int subtraction can overflow. For example, a large negative minus a large positive wraps round to a positive number, so the comparator gives the wrong sign. Integer.compare(a, b) never overflows.
Follow-up: What do you write instead? Integer.compare(a, b), or better Comparator.comparingInt(...). Both return the correct sign for every pair of ints.
Common mistake: Saying subtraction is bad because it is slow. The real problem is that it gives a wrong order silently for large values, with no exception.
Lesson: Comparable vs ComparatorIt must be antisymmetric (the sign of compare(a, b) is the opposite of compare(b, a)), transitive (if a > b and b > c, then a > c), and consistent over time. Breaking these can make sort throw "Comparison method violates its general contract".
Follow-up: What error shows up when the contract is broken? IllegalArgumentException: Comparison method violates its general contract!, thrown by TimSort when it notices inconsistent results.
Common mistake: Using a random or time-based value inside compare. Two calls on the same pair then give different answers and the sort breaks.
The second call throws IllegalStateException. Each remove() must follow its own next(), because it removes the element that next() last returned.
Follow-up: Write the loop in your head: how should it look? while (it.hasNext()) { String s = it.next(); if (bad(s)) it.remove(); }. The next() always comes before the remove().
Common mistake: Calling it.remove() before any next(). That also throws IllegalStateException.
It does not throw, but after remove(i) the next element shifts into index i and the loop's i++ skips it. Loop from the end or use removeIf.
Follow-up: Does that forward index loop throw CME? No, there is no iterator, so nothing checks modCount. It silently skips elements instead.
Common mistake: Believing the index loop throws CME. Silent skipping is the more dangerous bug, because no error tells you.
Lesson: Fail-Fast Iterators and ConcurrentModificationExceptionArrays need their exact element type at runtime to check every store, but T is erased. Use a List<T>, or Array.newInstance(type, n) with a Class<T> token and an unchecked cast.
Follow-up: How do you build a generic array anyway? Use (T[]) new Object[n] with a suppressed unchecked warning inside a class that never leaks the array, or Array.newInstance(type, n). A List<T> is usually simpler.
Common mistake: Saying new T[n] is fine because T is a class. T is erased, so the array's real element type is unknown.
A type parameter with a limit, like <T extends Number>. It lets you call the bound's methods, such as doubleValue(), and it also decides what T erases to.
Follow-up: Why does a bound use extends even for an interface? extends in a bound means "is a subtype of", for classes and interfaces alike. implements is not allowed inside < >.
Common mistake: Writing <T implements Comparable<T>>. The Java compiler stops with a syntax error.
A LinkedHashSet<String>: it rejects duplicates in O(1) and remembers insertion order.
Follow-up: What if they also want fast lookup by email? A LinkedHashMap<String, User> keyed by email: O(1) lookup and the sign-up order is kept.
Common mistake: Choosing a List and calling contains for every new email. That is O(n) per check and O(n squared) overall.
Loop over the words and call counts.merge(word, 1, Integer::sum) on a HashMap, O(n). With streams: Collectors.groupingBy(w -> w, Collectors.counting()).
Follow-up: How do you print the counts alphabetically? Count in a HashMap, then copy into a TreeMap once for printing, or count straight into a TreeMap if the data is small.
Common mistake: Writing counts.put(w, counts.get(w) + 1). For a new word get returns null and the unboxing throws NullPointerException.
Stack extends the synchronized legacy Vector, so it is slower and exposes index methods that break stack rules. ArrayDeque is faster and its push, pop and peek work on one end only.
Yes. The automatic cast can overflow silently: byte b = 120; b += 10; gives -126. It also hides narrowing in cases like int x = 5; x += 2.7;, which makes x 7.
Follow-up: Can you give an example? byte b = 120; b += 10; gives -126, and int x = 5; x *= 1.5; gives 7, because the double result 7.5 is cast back to int.
Common mistake: Saying a compound assignment is exactly x = x + y. It adds a hidden cast, which is why it can lose data.
Use Math.addExact, subtractExact, multiplyExact or toIntExact, which throw ArithmeticException on overflow. Or do the math in long and check the range, or use BigInteger.
Postfix x++, then prefix ++x and unary minus, then * / %, then + -, then shifts, comparisons, ==, &&, ||, the ternary ?:, and assignment last. When in doubt in real code, add brackets.
Because String methods never modify the object; they return a new String. The statement throws the result away. Write s = s.toUpperCase();.
Follow-up: How do you keep the result? Assign it: s = s.toUpperCase();. Strings are immutable, so every method returns a new String.
Common mistake: Saying toUpperCase() changes s. It builds a new String and the statement throws it away.
0.0 == -0.0 true but Double.valueOf(0.0).equals(-0.0) false?== on primitives treats 0.0 and -0.0 as equal, but Double.equals compares the bit patterns, which differ. NaN is the opposite case: NaN == NaN is false, but Double.valueOf(Double.NaN).equals(Double.NaN) is true.
Inside the child, use super.name. From outside, cast the reference to the parent type: ((Parent) child).name.
Common mistake: Using this.name in the child. That is the child's own field; the hidden parent field needs super.name.
Yes. Fields are hidden, not overridden, so final does not stop the child from declaring its own field of that name; which one you read depends on the reference type.
The finally return wins and replaces the try's value. It is legal, but compilers and code reviewers flag it because it hides the try's intent.
Common mistake: Answering 1 because the return in try came first. The return in finally replaces it.
catch (IOException | Exception e) not compile?The types in a multi-catch must not be subclasses of each other, and IOException is already covered by Exception, so the compiler rejects it. Also, e is implicitly final inside a multi-catch.
I track three things: which statement throws, which catch matches first from inner to outer, and what return value or exception is in flight when finally runs. Then I check whether finally returns or throws, which overrides everything.
Common mistake: Reading the code only top to bottom. Track the throw, the first matching catch from inner to outer, and what finally does to the return value or exception.
while (it.hasNext()) { if (it.next() > max) max = it.next(); }?next() is called twice per iteration, so the code compares one element but stores the next one, and it can throw NoSuchElementException at the end. Store it.next() in a local variable once.
Follow-up: How do you fix it? Read the value once: int x = it.next(); if (x > max) max = x;.
Common mistake: Missing that next() moves forward on every call, so two calls read two different elements.
Not in a find-the-bug round. Make the smallest change that fixes the root cause, explain it, and then mention any refactoring you would suggest separately.
Lesson: Finding, Explaining and Preventing the BugOutput printed before that point still appears. After an uncaught exception, finally blocks on the way out still run and then the program stops. System.exit stops the JVM at once, so finally blocks do not run.
Follow-up: What about the code after it? It never runs, because the exception leaves main or the JVM stops.
Common mistake: Forgetting that the output printed before the exception still appears.
Lesson: Practice: Output Prediction & Debugging Mock TestThe bound misses divisors: for n = 4 the loop never runs, so 4 is called prime. Loop while i * i <= n, and treat n < 2 as not prime.
Common mistake: Checking only up to n / 2 and not guarding n < 2. For 4 the loop never runs, so 4 is called prime.
Lesson: Practice: Output Prediction & Debugging Mock TestA sorted input opens two pointers and binary search, often removing the need for extra memory. If it is not sorted, I ask whether I may sort it or must keep the original order.
Lesson: Clarifying Questions and Edge CasesWhen the second a arrives, its earlier position is before the current window start. Without the prev >= start check, the window would jump backwards and give a wrong answer.
Follow-up: How does your code handle it? It keeps each character's last index and moves the window start only forward, using the larger of the old start and last index plus one. The second a in "abba" then cannot pull the window back.
Common mistake: Moving the window start to the last index plus one without checking it is already behind the start. On "abba" the start jumps backwards and the answer becomes 3 instead of 2.
Lesson: Clarifying Questions and Edge CasesNo. Usually I describe it in one or two sentences with its complexity and move to a better idea. I code it only if the interviewer asks, or if I cannot find an improvement and need a working answer.
Follow-up: When would you code it? If the interviewer asks for it, if I cannot find an improvement, or if I want a test oracle to check the fast version against.
Common mistake: Typing out the full brute force "to be safe" and running out of time before the real solution. Describe it and move on.
Lesson: Brute Force First, Then ImproveUsually the HashMap version: one pass, O(n), and it can return original indices. If memory were tight or the array were already sorted, I would choose two pointers.
Follow-up: And if you must return the original indices? Then the HashMap storing value to index, because sorting loses the original positions.
Common mistake: Always choosing one version. The right choice depends on memory, sorted input and what the method must return.
Lesson: Brute Force First, Then ImproveI say the final time and space complexity with the reason, name the edge cases it handles, and mention any alternative with a different trade-off in one sentence.
Lesson: Brute Force First, Then ImproveStrings are immutable, so each += creates a new String and copies all existing characters. Over n appends that is 1 + 2 + ... + n copies, about n²/2. StringBuilder.append is amortized O(1).
Follow-up: How do you fix it? Use a StringBuilder and append, which is amortized O(1) per character, then call toString once.
Common mistake: Saying "it is O(n) because there is one loop". The loop is O(n) iterations, but each += is O(length), so the total is O(n²).
Lesson: Stating Time and Space ComplexityFor n = 10⁶, log₂ n is about 20, so it is around 20 times more steps in the worst case, still fast. Constants and memory access often matter as much, so I mention both when comparing.
Lesson: Stating Time and Space ComplexityUsually not; most interviewers do not expect them. I may say "assuming java.util imports" in one sentence and move on.
Lesson: Coding on a Whiteboard or Paper in Offline InterviewsI dry run one example with a table, then walk through the edge cases aloud and, if time allows, write a short main with test calls and expected values.
Follow-up: What if the interviewer cannot see the table? I write the table in a clear corner and point at each row as I fill it, so they can follow without asking.
Common mistake: Saying "I tested it in my head". The interviewer needs to see the table to believe it.
Lesson: Coding on a Whiteboard or Paper in Offline InterviewsThe day before, I check that Java and my IDE work, a blank template compiles, the font is large, notifications are off, and camera, mic and headphones work in the meeting tool. I keep the laptop charging and a mobile hotspot ready.
Follow-up: What do you do five minutes before the call? Only a short check: charger, hotspot ready, notifications off and the template open. The real setup was done the day before.
Common mistake: Installing or updating tools just before the call. A forced update or a missing JDK can cost the first ten minutes.
Lesson: Live Coding on a Shared Screen in Online InterviewsI treat it like a whiteboard: compile mentally, dry run one example with a variable table, and walk through the edge cases aloud.
Lesson: Live Coding on a Shared Screen in Online InterviewsI apologise briefly, mute if needed, and handle it quickly. It is better to tell the interviewer than to look distracted, and I try to book a quiet room in advance.
Lesson: Live Coding on a Shared Screen in Online InterviewsA sorted array with a pair or triplet condition, a palindrome check, or in-place rearranging like removing duplicates or moving zeroes. The pointers walk inwards or one chases the other.
Lesson: Frequently Asked Problem Types by Data StructureMemoization is top-down: a recursive function that caches results. Tabulation is bottom-up: a loop that fills a table from the base cases. Both have the same complexity; tabulation avoids deep recursion.
Follow-up: Which would you write in an interview? Usually bottom-up, because it avoids deep recursion and the table order is explicit. I mention memoization as the top-down alternative.
Common mistake: Saying one is faster. Both have the same complexity; the difference is recursion versus a loop and the order of computation.
Lesson: Frequently Asked Problem Types by Data StructureI name one honestly, such as DP, explain how I practise it (state, recurrence, small table by hand) and mention a problem I can now solve. It shows self-awareness and a plan.
Follow-up: Which problem can you solve now? I name a real one, for example coin change, and explain its recurrence in a sentence. It proves the practice is working.
Common mistake: Saying "none" or "all of them". Both avoid the question; an honest topic with a plan shows self-awareness.
Lesson: Frequently Asked Problem Types by Data StructureWhether the input is sorted, whether touching intervals like [1, 4] and [4, 5] should merge, whether I may modify the input, and the output order.
Lesson: Practice: DSA Mock Coding RoundI test more edge cases, mention the complexity and any trade-offs, and offer a follow-up myself, such as an in-place version or how to handle very large input.
Lesson: Practice: DSA Mock Coding RoundI would consider it for large, CPU-heavy, independent work on a source that splits well, like an array or ArrayList, and only after measuring. I avoid it for small lists, I/O work, LinkedList sources, and any code with shared mutable state, because thread overhead or race conditions make it slower or wrong.
Follow-up: How do you decide if it is worth it? I measure. I run the sequential and parallel versions on realistic data, and keep parallel only if it is clearly faster and the lambdas are pure.
Common mistake: Saying "always, because it is faster". Splitting and combining cost time, so small lists are often slower in parallel.
Lesson: Intermediate vs Terminal Operations and LazinessBoth return true, because no element breaks the rule. anyMatch on an empty stream returns false.
For the result it can, if map changes what filter tests. For speed it often matters: filtering first means fewer elements reach the costly map. Interviewers like the answer "filter as early as possible".
Follow-up: Give an example where the order changes the result. map(x -> x * 2).filter(x -> x > 10) keeps different numbers than filter(x -> x > 10).map(x -> x * 2), because the test sees the doubled value in the first one.
Common mistake: Saying the order never matters. It changes the result when map changes the value filter tests, and it changes the speed even when it does not.
Arrays.stream(text.split(" ")).collect(Collectors.groupingBy(w -> w, TreeMap::new, Collectors.counting())). I would add .map(String::toLowerCase) before collecting if case should not matter, and TreeMap::new gives a sorted, predictable output.
Follow-up: How would you print the three most frequent words? Stream the entries of the map, sort them by value in reverse order, then limit(3): freq.entrySet().stream().sorted(Map.Entry.<String, Long>comparingByValue().reversed()).limit(3).
Common mistake: Forgetting toLowerCase, so Java and java are counted as two different words.
s.chars().mapToObj(c -> (char) c).collect(groupingBy(c -> c, LinkedHashMap::new, counting())). LinkedHashMap keeps the first-seen order, which also helps to find the first non-repeated character.
Follow-up: Why LinkedHashMap here? It keeps the first-seen order, so the first entry with count 1 is the first non-repeated character.
Common mistake: Using HashMap and then claiming the first entry with count 1 is the first non-repeated character. HashMap has no order.
map.entrySet().stream().sorted(Map.Entry.comparingByValue(Comparator.reverseOrder())).collect(toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> a, LinkedHashMap::new)). The LinkedHashMap is essential, or the sorted order is lost.
map applies a function that returns a plain value and wraps the result. flatMap is for a function that already returns an Optional, so the result is not wrapped twice. Using map there gives Optional<Optional<T>>.
Follow-up: What type do you get from map when the function returns an Optional? Optional<Optional<T>>, a box inside a box. flatMap removes the extra layer and gives Optional<T>.
Primitive versions that avoid boxing. They come from primitive streams, for example IntStream.of(3, 9).max() returns an OptionalInt, read with getAsInt() or orElse(0).
equals is a public method of Object, and every implementation already has it, so it does not count as an abstract method for the rule. Only compare is left.
Common mistake: Saying Comparator has two abstract methods. equals matches a public Object method, so it does not count.
The first argument becomes the object the method is called on, and the second becomes the method's parameter: (a, b) -> a.compareTo(b).
(a, b) -> Integer.compare(b.marks(), a.marks()), or more readably Comparator.comparingInt(Student::marks).reversed(). I avoid b.marks() - a.marks() because subtraction can overflow for large values.
Follow-up: Why not subtract the marks? b.marks() - a.marks() can overflow for large values and give the wrong sign. Integer.compare is always safe.
Period measures date-based amounts in years, months and days, like an age or a course length. Duration measures time-based amounts in seconds and nanoseconds, like a train journey or a timeout.
Follow-up: Which one would you use to add one month to a date? A Period or plusMonths(1). A Duration is a fixed number of seconds, and a month is not a fixed number of seconds.
Common mistake: Using Duration for dates. Duration.between on two LocalDate objects throws UnsupportedTemporalTypeException.
LocalDate.now(), LocalDateTime.now(), ZonedDateTime.now(ZoneId.of("Asia/Kolkata")) or Instant.now(). For tests, each has an overload that takes a Clock, so time can be fixed.
s.chars().mapToObj(c -> (char) c).collect(groupingBy(identity(), LinkedHashMap::new, counting())). chars() gives an IntStream, so I convert to Character first.
Follow-up: Why mapToObj? chars() gives an IntStream of int values, and groupingBy needs objects. mapToObj(c -> (char) c) converts each int to a Character.
Common mistake: Using HashMap and then talking about the "first" character. Only LinkedHashMap keeps first-seen order.
students.stream().allMatch(s -> s.marks() >= 40). It short-circuits at the first failure, and returns true for an empty list.
Common mistake: Forgetting that allMatch returns true for an empty list, so an empty class counts as "all passed". Mention that edge case.
Runnable.run() returns nothing and cannot throw checked exceptions. Callable.call() returns a value and can throw checked exceptions, and it is used with ExecutorService.submit, which gives back a Future.
Follow-up: How do you get the result of a Callable? Submit it to an ExecutorService. submit returns a Future, and get() blocks until the result is ready or throws ExecutionException.
Common mistake: Mentioning only the return value. Callable can also throw checked exceptions, which is the other half of the difference.
Lesson: Thread vs Runnable vs CallableIt hints to the scheduler that the current thread is willing to give up the CPU. It is only a hint, may do nothing, and is rarely useful in application code.
Lesson: Thread vs Runnable vs CallableThe Thread object of the thread executing that line. It is used to get the name, check the interrupt flag, or restore it with Thread.currentThread().interrupt().
Each lesson has quizzes, practice and more interview questions on the same topic.
Open the first lesson