Core Java Interview Questions with Answers
Interview Questions 13 min read
The topics Java interviews actually probe — equals and hashCode, the memory model, collections, exceptions, generics — with what each question is really testing and the follow-up that separates a memorised answer from an understood one.
Most Java interview lists are answer keys. That is the wrong shape for preparation, because interviewers rarely stop at the first answer. They ask the follow-up, and the follow-up is where a memorised definition falls apart.
This is organised by the six areas that come up most, and for each one: what is being tested, the answer that satisfies it, and the follow-up you should expect.
Written against Java 17.
1. equals and hashCode
What is being tested: whether you understand that collections depend on a contract, not on intuition.
The contract has two halves. If a.equals(b) then a.hashCode() == b.hashCode(), mandatory. The
converse is not required: two unequal objects may share a hash code, and that is a collision, not a
bug.
Override one without the other and hash-based collections break. Given equals but not hashCode,
two equal objects land in different buckets, so a HashMap stores both and get finds neither. Given
hashCode but not equals, two identical keys are different keys.
public record Point(int x, int y) { } // both generated, consistent
A record is the correct answer in modern Java, and saying so demonstrates more than writing the methods out.
The follow-up: what happens if you mutate a key after putting it in a HashMap? The entry is
filed under the old hash code and becomes unreachable, still counted by size(), invisible to get.
Keys must be effectively immutable. Being able to say that is the difference between having read the
contract and having used it.
The other follow-up: is it legal for hashCode to return a constant? Yes, and it is correct but useless: every key collides, and the map degrades to a linked list. Since Java 8 a bucket past eight entries becomes a red-black tree, bounding the damage at O(log n).
2. The memory model
What is being tested: whether “thread safety” means anything specific to you.
A race condition is two problems, and conflating them is the usual failure:
Atomicity. count++ is a read, an add and a write. Two threads can read 41, both compute 42, and
one increment is lost.
Visibility. A thread is not guaranteed to observe another thread’s write at all. The memory model
permits values to sit in registers or cache lines; without a happens-before relationship there is no
promise the reader ever sees it. A loop polling a plain boolean flag can spin forever, and will
under optimisation.
volatile fixes visibility and not atomicity. It is correct for a flag written once and read often,
and wrong for a counter.
private volatile boolean running = true; // correct
private volatile int count = 0; // count++ is still not atomic
The follow-up: how would you make a counter thread-safe? Three answers, in ascending order of
what they demonstrate: AtomicInteger for a single variable; synchronized or a ReentrantLock when
several fields change together; or do not share mutable state at all, an immutable value has no race.
The third answer is the one senior interviewers are listening for.
The other follow-up: what does synchronized guarantee besides mutual exclusion? It establishes
happens-before: everything written before releasing a monitor is visible to the next thread that
acquires it. That is why a correctly synchronised counter needs no volatile.
3. Collections
What is being tested: whether you choose by behaviour or by habit.
The distinctions worth having ready:
ArrayListvsLinkedList.ArrayListfor nearly everything.LinkedList’s O(1) insertion requires an iterator already at the position;get(n)is O(n), and its per-node overhead defeats cache locality.ArrayDequefor a queue or stack.HashMapvsLinkedHashMapvsTreeMap. No order, insertion (or access) order, sorted order.LinkedHashMapwithremoveEldestEntryis an LRU cache in about five lines.HashMapvsHashtable.Hashtableis legacy: synchronised on every method, no nulls. The modern answer isConcurrentHashMap, and knowing why matters more, see the follow-up.- Fail-fast vs weakly consistent iterators.
ArrayList’s iterator throwsConcurrentModificationExceptionwhen the list changes underneath it.ConcurrentHashMap’s never throws and may or may not reflect concurrent updates.
The follow-up: why is ConcurrentHashMap better than a synchronized map? Not primarily
performance. Collections.synchronizedMap serialises all access behind one lock, and, crucially —
compound operations are still not atomic:
Integer n = map.get(k);
map.put(k, n == null ? 1 : n + 1); // broken under concurrency, synchronized or not
map.merge(k, 1, Integer::sum); // atomic on ConcurrentHashMap
Being able to name that gap is a much stronger answer than “it locks per segment”.
The other follow-up: what happens if you remove from a list inside a for-each?
ConcurrentModificationException, usually. Removing the second-to-last element exits the loop early
without throwing, because the iterator’s cursor now equals the reduced size. That is worth knowing;
it is a real bug that produces no exception.
4. Exceptions
What is being tested: whether you have thought about error handling as design rather than syntax.
Checked exceptions extend Exception and must be declared or caught; unchecked extend
RuntimeException and need not be. Error is for conditions an application should not attempt to
handle, OutOfMemoryError, StackOverflowError.
The design question behind it: a checked exception says the caller can plausibly recover. A file that might not exist, a network that might be down. An unchecked exception says a programmer made a mistake, a null where one was not allowed, an index out of range.
The follow-up: what does finally do if the try block returns? It runs before the value is
handed back. And a return inside finally replaces the one from try, discarding it, including
discarding a thrown exception:
static int broken() {
try {
throw new IllegalStateException("lost");
} finally {
return 1; // swallows the exception entirely
}
}
Never return from finally. Interviewers ask this because the answer is unintuitive and the mistake
is real.
The other follow-up: what is try-with-resources for? Deterministic closing, and it handles the
case a hand-written finally usually gets wrong, if both the body and close() throw, the exception
from close() is attached as a suppressed exception rather than replacing the original. A manual
finally { stream.close(); } loses the first one.
5. Generics
What is being tested: whether you know that generics are a compile-time construct.
Type erasure: List<String> and List<Integer> are the same class at runtime. The compiler checks
types and inserts casts, then discards the parameter. Hence:
List<String> a = new ArrayList<>();
List<Integer> b = new ArrayList<>();
a.getClass() == b.getClass(); // true
// and these are impossible
new T[10]; // cannot create an array of a type parameter
if (obj instanceof List<String>) { } // cannot test an erased parameter
The follow-up: what does ? extends mean, and why can I not add to such a list?
List<? extends Number> numbers = new ArrayList<Integer>();
numbers.add(1); // compile error
Number n = numbers.get(0); // fine
? extends Number means “some specific subtype of Number, unknown here”. Since it might be
List<Double>, adding an Integer cannot be proven safe, so reading is allowed and writing is not.
? super Integer is the mirror: you can add an Integer, and reads only give you Object.
The mnemonic is PECS, Producer Extends, Consumer Super. Quoting it is fine; explaining why is better.
6. String
What is being tested: whether you understand immutability and interning.
String is immutable, so every apparent modification allocates. Concatenating in a loop with +
allocates each iteration and is O(n²) overall; StringBuilder is O(n).
String a = "hello";
String b = "hello";
String c = new String("hello");
a == b; // true — both refer to the same interned literal
a == c; // false — new String allocates
a.equals(c); // true
Literals are interned in a shared pool, so identical literals are one object. new String(...)
deliberately is not.
The follow-up: why must String be immutable? Three reasons worth having: the string pool would
be unsafe to share if strings could change; hashCode is cached, which requires the contents to be
fixed; and strings are used as security-sensitive values, a mutable class name or file path could be
changed after a check and before its use.
How to answer
A pattern that consistently works better than reciting:
- Answer the question directly, in one or two sentences.
- Say why it is that way, the constraint that forces the design.
- Name the failure mode it prevents or causes.
- Stop. Volunteering everything you know invites a question you cannot answer, and buries the part they were listening for.
And say “I do not know, but I would find out by…” when you do not know. Interviewers are calibrating what you do at the edge of your knowledge, and a confident wrong answer is far worse than a named gap.
Frequently asked questions
Which topics come up most often?
equals/hashCode, collections, the memory model and
concurrency, exceptions, generics and String. Everything else tends to be a variation or something
role-specific.
Do I need to memorise complexity tables?
Not as tables. You need ArrayList versus LinkedList
and HashMap versus TreeMap with the reasoning, because the follow-up is always “why”.
Is it acceptable to say I would use a record?
Yes, and it is the better answer for a value class.
It shows current knowledge and removes any chance of an inconsistent equals/hashCode.
How much should I say about virtual threads?
Enough to place them: Java 21 makes threads cheap so blocking scales, and it changes nothing about races or deadlocks. Overclaiming here is a common misstep.
What if I do not know an answer?
Say so, then say how you would find out. That is a better signal than guessing, and interviewers are explicitly assessing it.
Are brainteasers still asked?
Rarely, and mostly at companies you can screen out. Expect practical questions about code you would actually write.
Should I ask clarifying questions?
Always, for anything that sounds like a design or coding task. Input size, constraints, whether duplicates or nulls are possible: the assumptions change the answer, and asking demonstrates you know that.
How deep do JVM internals go?
For most roles: heap versus stack, what garbage collection does,
what OutOfMemoryError means. Collector tuning specifics come up only where the role involves it.
Do I need to write code on a whiteboard?
Often, and it is usually not about syntax. Narrate your approach, state the complexity, and mention what you would test.
What is the single most common weak answer?
“volatile makes it thread-safe.” It addresses
visibility only, and the person asking is waiting to see whether you know the difference.
Where should I go next?
Java concurrency basics covers the memory-model section in depth, HashMap covers the collections questions, and the interview guides cover other areas.