Classic questions on ArrayList vs Vector vs arrays, HashMap vs Hashtable vs ConcurrentHashMap, hashCode, Collection vs Collections, fail-fast vs fail-safe iterators, Map vs Queue, LinkedHashMap vs PriorityQueue, Comparable vs Comparator, BitSet, streams, heap vs stack, processes vs threads, context switching, multithreading benefits, wait/notify/notifyAll/sleep, BlockingQueue, CyclicBarrier, CompletableFuture, JDBC statement types, NIO and memory-mapped buffers — with senior-level depth and corrections.
Published September 25, 2026
Answer each question in one line, then add one production insight: a complexity trade-off, a concurrency hazard, or a modern replacement. Several popular answers are subtly wrong (for example, "ConcurrentHashMap allows null values", or "fail-safe iterators copy the collection"), and those errors are corrected here.
ArrayList and Vector?Short answer:
Vector is a legacy (Java 1.0) class. Every method is synchronized, and it doubles its capacity when it grows.ArrayList is unsynchronised and faster, and grows by about 50%.Key points to cover:
Vector in new code. Per-method locking doesn't make compound operations safe ("check then add" still races).CopyOnWriteArrayList (read-mostly lists), Collections.synchronizedList plus external locking for iteration, or a concurrent queue.Learn it in depth → List, Set, and Map
HashMap and ConcurrentHashMap?Short answer:
HashMap isn't thread-safe. Concurrent writes can lose updates or corrupt its structure. It allows one null key and null values.
ConcurrentHashMap is thread-safe, with high concurrency:
computeIfAbsent, merge, compute).It allows no null keys or values: null would be ambiguous with "absent" in concurrent get calls.
Common trap: saying ConcurrentHashMap only disallows null keys. Values can't be null either.
Learn it in depth → ConcurrentHashMap & CopyOnWriteArrayList
HashMap and Hashtable?Short answer:
Hashtable is legacy: fully synchronised (one lock for the whole table), with no null keys or values. It extends the obsolete Dictionary class, and has an Enumeration API.HashMap is unsynchronised and faster. It allows one null key and null values. Since Java 8, it also treeifies large buckets (red-black trees once a bin has more than 8 entries, and the table has at least 64 buckets), which protects against collision attacks.Use HashMap for single-threaded code, and ConcurrentHashMap for concurrent code. There's no reason to use Hashtable today.
hashCode() for?Short answer: It returns an int used by hash-based collections to pick a bucket. HashMap spreads the bits (h ^ (h >>> 16)), then masks with table.length - 1. A good hash distributes keys evenly, which keeps lookups at O(1) on average.
Key points to cover:
Object.hashCode() isn't a memory address in modern JVMs (typically a thread-local random value, stored in the object header).String caches its hash.Collection and Collections?Short answer:
Collection<E> is the root interface of lists, sets and queues. Maps are separate.Collections is a utility class of static helpers: sort, binarySearch, unmodifiableList, synchronizedMap, emptyList, nCopies, frequency, shuffle.Key points to cover:
List.of/copyOf for immutable collections, list.sort(comparator), and streams.Short answer:
ArrayList, HashMap) track a modCount. If the collection is structurally modified during iteration by anything other than the iterator's own remove(), the next next() throws ConcurrentModificationException. This is a best-effort check, not a thread-safety guarantee.CopyOnWriteArrayList iterates over the array as it was when the iterator was created;ConcurrentHashMap, ConcurrentLinkedQueue traverse live data, never throw CME, and may or may not reflect concurrent updates.Common trap: saying fail-safe iterators always work on a clone. Only copy-on-write collections snapshot. ConcurrentHashMap doesn't copy anything.
list.removeIf(item -> item.isExpired()); // the safe way to remove while "iterating"
for (Iterator<Item> it = list.iterator(); it.hasNext();) if (it.next().isExpired()) it.remove();
Learn it in depth → Iterators & Modification Semantics
Map and Queue?Short answer:
Map<K,V> holds key → value associations, with unique keys. It's designed for lookup by key (HashMap is O(1) average, TreeMap is O(log n) and sorted).
Queue<E> is a Collection that holds elements for processing, in a defined order:
ArrayDeque, LinkedList);PriorityQueue);BlockingQueue).It has offer/poll/peek, which return special values, and add/remove/element, which throw.
Map doesn't extend Collection, and Queue does.
LinkedHashMap and PriorityQueue?Short answer: They solve different problems:
LinkedHashMap is a map with a doubly linked list through its entries, which gives predictable insertion order, or access order (new LinkedHashMap<>(16, 0.75f, true)). With removeEldestEntry, it's the classic LRU cache. O(1) operations.PriorityQueue is a binary heap of elements, ordered by natural order or a Comparator:
offer/poll are O(log n), and peek is O(1);poll order is;PriorityBlockingQueue).class LruCache<K, V> extends LinkedHashMap<K, V> {
private final int max;
LruCache(int max) { super(16, 0.75f, true); this.max = max; }
@Override protected boolean removeEldestEntry(Map.Entry<K, V> e) { return size() > max; }
}
Comparable and Comparator?Short answer:
Comparable<T>: the class defines its natural ordering itself, with compareTo(T). That's one ordering per class (String, LocalDate, BigDecimal). It's used by TreeMap/TreeSet and Collections.sort when no comparator is given.Comparator<T>: an external, pluggable ordering, with compare(a, b). You can have many orderings without touching the class. Java 8's builders compose them:orders.sort(Comparator.comparing(Order::priority)
.thenComparing(Order::createdAt, Comparator.reverseOrder())
.thenComparing(Order::id));
Key points to cover:
compareTo consistent with equals, or sorted sets and maps behave surprisingly. BigDecimal("2.0") and ("2.00") compare equal, but aren't equals.a.x - b.x): it overflows. Use Integer.compare.BitSet used for?Short answer: A growable vector of bits, backed by a long[], with fast set/clear/flip/get, bulk operations (and, or, xor, andNot), cardinality(), and nextSetBit() for iteration. It's very memory-efficient for dense flags over integer indexes: 1 bit per flag, instead of a boolean (1 byte) or a Boolean object.
It's used for sieve algorithms, feature and permission masks, visited sets in graph algorithms, simple Bloom filters, and bitmap indexes. It isn't thread-safe. For sparse, huge bitmaps, use RoaringBitmap.
Short answer: A declarative pipeline for processing a sequence of elements:
IntStream.range, Files.lines;filter, map, flatMap, sorted, distinct;collect, reduce, forEach, findFirst, count.A stream doesn't store data, and can be consumed only once. Laziness enables short-circuiting and operation fusion. Primitive streams (IntStream) avoid boxing, and parallel() splits the work across the common ForkJoinPool.
Key points to cover:
toList(), and Java 22+ added gatherers (Stream.gather) for custom intermediate operations.Learn it in depth → Streams API
Short answer:
-Xss. Overflowing it throws StackOverflowError (deep recursion).-Xms/-Xmx, and exhausting it throws OutOfMemoryError: Java heap space.Key points to cover:
Learn it in depth → JVM Memory Areas
Collection, and between an array and an ArrayList?Short answer:
int[]) or references;Object[] a = new String[1]; a[0] = 1; throws ArrayStoreException at runtime;length, and the Arrays utilities).ArrayList:
Common trap: saying collections "store elements of different types". With generics, a List<Order> is homogeneous, like an array. Use List<Object> only if you really want mixed types.
Short answer:
Key points to cover:
Short answer:
The costs:
Use higher-level tools (executors, CompletableFuture, virtual threads, concurrent collections), rather than raw threads.
Short answer: The OS (or the JVM, for virtual threads) saves one thread's execution state (registers, program counter, stack pointer) and restores another's, so they can share a CPU core. It happens on time-slice expiry, blocking I/O, lock contention, sleep and wait. Each switch costs microseconds, plus cache and TLB pollution.
Key points to cover:
notify() and notifyAll()?Short answer:
notify() wakes one arbitrary thread waiting on that object's monitor.notifyAll() wakes all of them. They then compete for the monitor, and each re-checks its condition.Prefer notifyAll(), unless every waiter waits for the same condition, and any one of them can proceed. With notify(), the "wrong" thread may wake up (for example a producer when a consumer was needed), and the signal is lost, which can hang the system.
Key points to cover:
java.util.concurrent (BlockingQueue, Condition with separate notFull/notEmpty conditions, CountDownLatch) instead of wait/notify.wait() and notify()?Short answer: They're two halves of the guarded-wait protocol on an object's monitor:
wait() releases the monitor, and suspends the current thread until it's notified (or interrupted, or times out, or wakes spuriously). It then reacquires the monitor before returning.notify() signals one waiting thread. It doesn't release the lock: the woken thread proceeds only after the notifier leaves the synchronized block.Both must be called while holding that object's monitor, or they throw IllegalMonitorStateException. Always wait in a loop, re-checking the condition:
synchronized (lock) {
while (queue.isEmpty()) lock.wait(); // loop: spurious wakeups and competing consumers
item = queue.remove();
}
// producer
synchronized (lock) { queue.add(item); lock.notifyAll(); }
sleep() and wait()?Short answer:
Thread.sleep(ms) | obj.wait() | |
|---|---|---|
| Defined in | Thread (static) | Object |
| Lock | Keeps every lock it holds | Releases that object's monitor |
| Requires a monitor | No | Yes (inside synchronized(obj)) |
| Wakes on | Timeout, or interrupt | notify/notifyAll, timeout, interrupt, or spuriously |
| Purpose | Pause execution | Wait for a condition signalled by another thread |
Both throw InterruptedException. Restore the interrupt flag (Thread.currentThread().interrupt()) if you can't propagate it.
BlockingQueue?Short answer: A thread-safe Queue whose put() blocks when it's full, and whose take() blocks when it's empty. There are also timed offer/poll. It's the backbone of the producer-consumer pattern, and of ThreadPoolExecutor's work queue. Implementations:
ArrayBlockingQueue (bounded, one lock);LinkedBlockingQueue (optionally bounded, separate put and take locks);PriorityBlockingQueue;DelayQueue (scheduling);SynchronousQueue (a direct hand-off, with zero capacity);LinkedTransferQueue.Key points to cover:
LinkedBlockingQueue in an executor can grow until an OutOfMemoryError.CyclicBarrier?Short answer: A synchroniser where a fixed number of threads wait (await()) until all of them arrive. Then they're released together, optionally after a barrier action runs. It's reusable (cyclic) for the next phase. It's used for iterative parallel algorithms and simulations, where each phase must finish before the next begins.
Key points to cover:
BrokenBarrierException for the others).Phaser.CountDownLatch.Learn it in depth → Concurrent Utilities & Coordination
CompletableFuture.Short answer: A Future you can complete manually, and compose without blocking:
supplyAsync/runAsync;thenApply, chain async steps with thenCompose (flatMap), combine two with thenCombine, and wait for many with allOf/anyOf;exceptionally/handle/whenComplete;orTimeout, completeOnTimeout, Java 9).Key points to cover:
*Async methods run on ForkJoinPool.commonPool(). Pass your own executor for blocking I/O, or you'll starve the common pool, which parallel streams share.get() wraps exceptions in ExecutionException, and join() in CompletionException.Learn it in depth → CompletableFuture
Short answer:
Statement: static SQL, without parameters. It's vulnerable to SQL injection if you concatenate input, and is rarely appropriate.PreparedStatement: parameterised SQL (? placeholders). It prevents injection (the values are bound, never parsed as SQL), handles types correctly, and allows statement caching. Use it with addBatch/executeBatch for bulk inserts.CallableStatement: calls stored procedures, with IN/OUT parameters ({call proc(?, ?)}).Key points to cover:
JdbcTemplate/JPA).Learn it in depth → Practical SQL & JDBC for Interviews
Short answer: NIO (java.nio, Java 1.4, extended in NIO.2 in Java 7) provides:
ByteBuffer, including direct buffers allocated outside the heap for zero-copy I/O) and channels (FileChannel, SocketChannel), for block-oriented I/O.Selectors: one thread multiplexes thousands of connections. That's the foundation of Netty, Tomcat's NIO connector and WebFlux.java.nio.file): Path, Files, directory watching (WatchService), file attributes, and asynchronous channels.FileChannel.transferTo (zero-copy sendfile), and memory-mapped files.Key points to cover:
Short answer: FileChannel.map(...) returns a MappedByteBuffer that maps a region of a file directly into the process's virtual memory. Reads and writes become memory accesses, paged in and out by the OS page cache, with no copying through the heap and no explicit read calls. It's great for large files and random access (databases, Kafka's index files, Lucene).
Key points to cover:
int indexes).Arena, MemorySegment, FileChannel.map(..., arena)) gives deterministic unmapping, and 64-bit sizes.Q: What's the time complexity of HashMap.get, in the worst case?
A: O(1) on average. With many collisions in one bucket: O(log n) since Java 8, when the bucket is treeified (keys ideally Comparable), and O(n) in older versions, or with a tiny table.
Q: ArrayDeque or LinkedList for a stack or queue?
A: ArrayDeque. It's a circular array with better cache locality and no per-node allocation. Stack is legacy (it extends Vector), and LinkedList has high overhead per element.
Q: Why can't ConcurrentHashMap.size() always be exact?
A: Counts are maintained in striped CounterCells (like LongAdder), and updates are concurrent, so size() or mappingCount() is an estimate while writes are in progress. Don't build logic that depends on an exact concurrent size.
Q: Is PriorityQueue iteration sorted?
A: No. Only repeated poll() returns elements in priority order. The iterator traverses the underlying heap array. To get a sorted view, sort a copy.