9 Java Coding Interview Questions with Checked, Runnable Examples

Quick Overview
Practice nine Java coding exercises with checked examples, explicit contracts, integer and Unicode edge cases, safe concurrency, and relevant question links.
Java coding interview preparation works best when you can explain the input contract, the invariant, and the failure cases behind a solution. Remembering a collection method is useful; knowing when that method preserves the required behavior is more useful.
These nine exercises combine algorithmic reasoning with Java-specific details: integer overflow, reference identity, access ordering, class initialization, interruption, and atomic updates. The examples use original synthetic inputs to illustrate invariants and edge cases.
Save each Java block in a file matching its public class name. All nine compile without warnings and print the outputs stated below each block. Each has a main method with a small demonstration. Vary the cases below before comparing your answer.
1. Find two distinct indices whose values sum to a target
This version returns the first match encountered while scanning the second index from left to right, keeping the earliest index for each prior value. It returns an empty array when no pair exists. It does not promise the lexicographically smallest pair across every valid answer.
import java.util.*;
public class TwoSumExample {
static int[] find(int[] values, int target) {
Map<Integer, Integer> seen = new HashMap<>();
for (int j = 0; j < values.length; j++) {
long wanted = (long) target - values[j];
if (wanted >= Integer.MIN_VALUE && wanted <= Integer.MAX_VALUE) {
Integer i = seen.get((int) wanted);
if (i != null) return new int[] {i, j};
}
seen.putIfAbsent(values[j], j);
}
return new int[0];
}
public static void main(String[] args) {
System.out.println(Arrays.toString(find(new int[] {3, 3}, 6)));
}
}
The output is [0, 1]. Before each iteration, the map contains only earlier elements. Looking up before insertion prevents an element from pairing with itself. Computing the complement in long prevents an overflowing int subtraction from creating a false match.
Expected time is O(n) with ordinary hash-table assumptions; extra space is O(n). Try an empty array, no solution, repeated values, and two Integer.MAX_VALUE values with target -2: their mathematical sum is not -2, so they must not match. The public lexicographically smallest Two Sum pair question deliberately adds a different tie-breaking requirement.
2. Detect a cycle before reversing a linked list
Define ownership before changing links. This example permits in-place reversal of an acyclic list and rejects a cycle before any mutation. It uses Floyd's slow and fast pointers for the cycle check, then reverses the links in one pass.
public class LinkedListExample {
static class Node {
final int value;
Node next;
Node(int value) { this.value = value; }
}
static boolean hasCycle(Node head) {
Node slow = head, fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if (slow == fast) return true;
}
return false;
}
static Node reverse(Node head) {
if (hasCycle(head)) throw new IllegalArgumentException("cycle");
Node previous = null, current = head;
while (current != null) {
Node next = current.next;
current.next = previous;
previous = current;
current = next;
}
return previous;
}
public static void main(String[] args) {
Node a = new Node(1), b = new Node(2), c = new Node(3);
a.next = b; b.next = c;
for (Node n = reverse(a); n != null; n = n.next) {
System.out.print(n.value + " ");
}
System.out.println();
}
}
The output is 3 2 1. Compare node references with ==; equal payloads do not imply the same node. Within a cycle, the faster pointer gains one position per iteration relative to the slower pointer, so they eventually meet. Without a cycle, the fast pointer reaches the end.
During reversal, previous heads the reversed prefix and current heads the unprocessed suffix. Save the next reference before overwriting it. Both passes are O(n) time and use O(1) auxiliary space. A shared list still needs an ownership or synchronization policy. Test null, one node, repeated payloads, a self-loop, and a cycle entering in the middle. The linked-node reversal prompt extends the discussion to recursive stack space.
3. Implement an LRU cache with access ordering
A bounded cache must keep its lookup and eviction order consistent. Here keys are integers, values are non-null strings, a missing lookup returns null, zero capacity retains nothing, and negative capacity is invalid.
import java.util.*;
public class LruExample {
private final Map<Integer, String> data;
LruExample(int capacity) {
if (capacity < 0) throw new IllegalArgumentException("capacity");
data = new LinkedHashMap<Integer, String>(16, 0.75f, true) {
protected boolean removeEldestEntry(Map.Entry<Integer, String> e) {
return size() > capacity;
}
};
}
String get(int key) { return data.get(key); }
void put(int key, String value) {
data.put(key, Objects.requireNonNull(value));
}
public static void main(String[] args) {
LruExample cache = new LruExample(2);
cache.put(1, "A"); cache.put(2, "B");
cache.get(1);
cache.put(3, "C");
System.out.println(cache.get(2) + " " + cache.get(1) + " " + cache.get(3));
}
}
The output is null A C: reading key 1 made it recent, so inserting key 3 evicted key 2. Oracle's LinkedHashMap reference distinguishes access order from insertion order and explains which operations update recency.
Ordinary lookup and insertion have expected O(1) cost, with O(capacity) retained storage, apart from fixed object overhead. This implementation is not thread-safe; even a successful get can change access order. A concurrent map alone would not synchronize a separate eviction list. If library shortcuts are forbidden, use a map plus a doubly linked list and preserve both structures on replacement and eviction. The Java Collections article covers the surrounding collection contracts.
4. Publish a lazily initialized singleton safely
The holder idiom uses class initialization rather than handwritten double-checked locking. The instance below has only immutable state.
public class SingletonExample {
final int value = 42;
private SingletonExample() {}
private static class Holder {
static final SingletonExample INSTANCE = new SingletonExample();
}
static SingletonExample get() { return Holder.INSTANCE; }
public static void main(String[] args) {
System.out.println(get() == get());
System.out.println(get().value);
}
}
The output is true, then 42. The JVM's class-initialization procedure provides initialization synchronization. The holder initializes on its first active use; ordinary access after initialization does not need a hand-written monitor.
If asked for double-checked locking, explain why the shared instance field needs volatile for safe publication. A thread-safe accessor does not make later mutations of the instance safe. Scope also matters: separate class loaders can load separate classes, and this example is not a defense against every reflective or serialization mechanism. For application code, discuss dependency injection and testability using the Spring Boot preparation article.
5. Coordinate a finite producer and consumer
This demonstration has one producer, one consumer, a capacity of two, and work values 1 through 3. The sentinel -1 is reserved and cannot be ordinary work. FIFO ordering places the termination marker after all three items.
import java.util.concurrent.*;
public class ProducerConsumerExample {
public static void main(String[] args) throws InterruptedException {
BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(2);
int[] total = {0};
Thread consumer = new Thread(() -> {
try {
while (true) {
int item = queue.take();
if (item == -1) return;
total[0] += item;
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
consumer.start();
try {
for (int item = 1; item <= 3; item++) queue.put(item);
queue.put(-1);
consumer.join();
System.out.println(total[0]);
} finally {
consumer.interrupt();
consumer.join();
}
}
}
The output is 6. Only the consumer updates total; the main thread reads it after a successful join. The bounded queue supplies backpressure, while the BlockingQueue API specifies blocking operations and the publication relationship between insertion and subsequent access.
The queue does not have an inherent close operation. More consumers need an explicit termination policy, such as an appropriate marker per consumer after producers finish. Executor shutdown() alone does not wake a task indefinitely blocked in take(). This small successful-run demonstration is not a production failure-recovery protocol: discuss worker failure, cancellation, deadlines, and delivery guarantees separately. Waiting time is not bounded by the queue's constant-time array operations.
6. Validate bracket nesting with a deque
The contract here accepts only ()[]{} characters, accepts the empty string, and rejects other characters rather than ignoring them. It is not a Java source-code parser.
import java.util.*;
public class BracketsExample {
static boolean valid(String text) {
Deque<Character> stack = new ArrayDeque<>();
String open = "([{", close = ")]}";
for (char c : text.toCharArray()) {
if (open.indexOf(c) >= 0) {
stack.push(c);
} else {
int kind = close.indexOf(c);
if (kind < 0 || stack.isEmpty() || stack.pop() != open.charAt(kind)) {
return false;
}
}
}
return stack.isEmpty();
}
public static void main(String[] args) {
System.out.println(valid("{[]}"));
System.out.println(valid("([)]"));
}
}
The output is true, then false. Before a closer is processed, the stack contains exactly the unmatched openings, most recent first. Counting opening and closing brackets cannot detect the crossing in ([)].
Time is O(n), and worst-case extra space is O(n). Check a leading closer, unclosed openings, an empty input, and an ordinary letter. If the specification allows comments or string literals, tokenization must precede structural matching. Practice the public bracket-sequence question after clarifying its own accepted-input contract.
7. Keep a bounded min-heap for the kth-largest occurrence
Duplicates occupy separate ranking positions in this version. For [5, 5, 4], the second-largest occurrence is 5; the second distinct value would be 4.
import java.util.*;
public class KthLargestExample {
static int select(int[] values, int k) {
if (k < 1 || k > values.length) throw new IllegalArgumentException("k");
PriorityQueue<Integer> heap = new PriorityQueue<>();
for (int value : values) {
heap.offer(value);
if (heap.size() > k) heap.poll();
}
return heap.element();
}
public static void main(String[] args) {
System.out.println(select(new int[] {5, 5, 4}, 2));
}
}
The output is 5. After each iteration, the heap retains the largest k occurrences seen so far, or all occurrences if fewer than k have arrived. Removing its minimum when it overflows preserves that invariant. Java's PriorityQueue places a least element at its head under natural ordering; iterating the queue does not produce a sorted traversal.
Time is O(n log(k + 1)), including the linear k=1 case; space is O(k). Validate k before reading the answer, and avoid subtraction-based comparators that can overflow. Compare selection, full sorting, and a bounded heap according to mutation and streaming constraints. The public kth-largest occurrence prompt provides related practice.
8. Make an increment atomic, then define the larger contract
count++ is a read-modify-write operation. volatile visibility rules do not make that sequence atomic. This example uses an atomic counter and waits for every worker before reading the final total.
import java.util.concurrent.atomic.AtomicInteger;
public class CounterExample {
public static void main(String[] args) throws InterruptedException {
AtomicInteger count = new AtomicInteger();
Thread[] workers = new Thread[4];
for (int i = 0; i < workers.length; i++) {
workers[i] = new Thread(() -> {
for (int j = 0; j < 10000; j++) count.incrementAndGet();
});
workers[i].start();
}
for (Thread worker : workers) worker.join();
System.out.println(count.get());
}
}
The output is 40000. A larger integer range requires its own overflow policy; atomicity does not prevent integer wraparound. Likewise, two separate atomic fields do not make a multi-field business update atomic. Synchronizing the complete operation may be the clearer answer.
For metrics under contention, compare LongAdder, whose documented sum operation is not an atomic snapshot during concurrent updates. Do not promise a fixed per-update wall-clock cost under contention, or treat one race-free-looking run of unsafe code as proof. The Java concurrency and increment-race question is a focused follow-up.
9. Reverse word order with an explicit whitespace rule
Here a word is a maximal sequence of code points for which Character.isWhitespace is false. The algorithm preserves each word's internal text and inserts one ASCII space between output words. Null is invalid; an empty or all-whitespace string returns an empty string.
public class ReverseWordsExample {
static String reverse(String text) {
StringBuilder out = new StringBuilder();
int end = text.length();
while (end > 0) {
while (end > 0 && Character.isWhitespace(text.codePointBefore(end))) {
end -= Character.charCount(text.codePointBefore(end));
}
int start = end;
while (start > 0 && !Character.isWhitespace(text.codePointBefore(start))) {
start -= Character.charCount(text.codePointBefore(start));
}
if (start < end) {
if (out.length() > 0) out.append(' ');
out.append(text, start, end);
}
end = start;
}
return out.toString();
}
public static void main(String[] args) {
System.out.println(reverse(" Java\tis\nfun "));
}
}
The output is fun is Java. Walking by code point applies Character.isWhitespace(int) to whole Unicode characters and steps over a supplementary character, such as an emoji, as one unit. Oracle's Character whitespace definition is not identical to every Unicode spacing category: a non-breaking space such as U+00A0 remains part of a word under this contract.
The scan and output construction take O(n) time with output-sized storage. Repeatedly concatenating an accumulated immutable string can instead cause repeated copying. This does not imply every Java concatenation expression allocates in the same way. Test tabs, newlines, blank input, punctuation, supplementary characters, and the non-breaking-space distinction before selecting trim, strip, or a regular-expression split as a replacement.
Check the explanation as well as the result
| Area | A useful check | A requirement that changes the answer |
|---|---|---|
| Hash lookup and ranking | Duplicates, no match, integer extremes | A different tie-breaking rule |
| Pointer mutation | Empty list, self-loop, repeated values | Shared ownership or cyclic input |
| Stateful collections | Read recency, replacement, zero capacity | Concurrent access or expiration |
| Concurrency | Termination and complete worker joins | Cancellation, failures, or multi-field state |
| Text processing | Empty input and whitespace semantics | Source-code parsing or language-aware words |
The HackerRank 2025 Developer Skills Report discusses developers' preference for problem-solving over memorization. It is survey context, not proof that a particular employer asks these exercises or scores them identically.
Use the coding interview patterns article to broaden the practice. For each Java answer, state the contract, explain why the invariant holds, test a counterexample, and change one requirement. If a library shortcut disappears or the input becomes concurrent, adapt the reasoning rather than reciting the same loop.
Comments (0)