Skip to content

Top 30 Java Coding Challenges with Solutions [2026]

September 28, 2026 5 min read
Java interview guide by Second Talent
TL;DR: Java coding rounds in 2026 still test hash maps, two pointers, sliding windows, heaps, graphs and dynamic programming. Interviewers now also expect modern Java in the answer: records, pattern matching for switch and ArrayDeque instead of Stack. JDK 25, released in September 2025, is the current long-term support release.

JDK 25 made a complete Java program as short as void main() { IO.println("Hi"); }, with no class and no imports. That release also finalized flexible constructor bodies and scoped values.

Most interview problems are the same as five years ago. The idiomatic answer is shorter, though: records, switch patterns and List.getLast() replace a lot of boilerplate.

Key takeaways
  1. 1The newest release is JDK 27, generally available since September 15, 2026. It makes G1 the default garbage collector in every environment.
  2. 2The java.util.Stack Javadoc itself says a Deque should be used in preference to it. ArrayDeque is the idiomatic stack and queue.
  3. 3Since JDK 24, a virtual thread that blocks inside synchronized no longer pins its carrier thread.
  4. 4JDK 21 gave every List the methods getFirst(), getLast() and reversed(), which several solutions below use.

Beginner Challenges

These check loops, strings, arrays and the core collections. A good candidate finishes each in a few minutes and then talks about edge cases without being asked.

1. Reverse a string without calling StringBuilder.reverse().

Task: return the characters of s in reverse order. "hello" returns "olleh".

static String reverse(String s) {
    char[] chars = s.toCharArray();
    for (int i = 0, j = chars.length - 1; i < j; i++, j--) {
        char tmp = chars[i];
        chars[i] = chars[j];
        chars[j] = tmp;
    }
    return new String(chars);
}

O(n) time and O(n) space for the copy. The trap is emoji. Each one is two chars (a surrogate pair), and this loop swaps the pair into an invalid order.

StringBuilder.reverse() keeps surrogate pairs together, which is the reason to use it in real code.

2. Check whether a sentence is a palindrome.

Task: ignore case and every character that is not a letter or digit. "A man, a plan, a canal: Panama" returns true.

static boolean isPalindrome(String s) {
    int i = 0, j = s.length() - 1;
    while (i < j) {
        char a = s.charAt(i), b = s.charAt(j);
        if (!Character.isLetterOrDigit(a)) { i++; continue; }
        if (!Character.isLetterOrDigit(b)) { j--; continue; }
        if (Character.toLowerCase(a) != Character.toLowerCase(b)) return false;
        i++;
        j--;
    }
    return true;
}

Two pointers give O(n) time and O(1) extra space. Building a cleaned, lowercased copy and comparing it with its reverse also works, but it uses O(n) memory.

3. Find the index of the first character that does not repeat.

Task: "leetcode" returns 0, "loveleetcode" returns 2, and "aabb" returns -1.

static int firstUnique(String s) {
    Map<Character, Integer> counts = new HashMap<>();
    for (char c : s.toCharArray()) counts.merge(c, 1, Integer::sum);
    for (int i = 0; i < s.length(); i++) {
        if (counts.get(s.charAt(i)) == 1) return i;
    }
    return -1;
}

Two passes, O(n) time. Map.merge replaces the older get, check for null, then put sequence. If the input is known to be lowercase ASCII, an int[26] is faster than a map.

4. Check whether two words are anagrams.

Task: the words contain only lowercase letters a to z. "listen" and "silent" return true.

static boolean isAnagram(String a, String b) {
    if (a.length() != b.length()) return false;
    int[] counts = new int[26];
    for (int i = 0; i < a.length(); i++) {
        counts[a.charAt(i) - 'a']++;
        counts[b.charAt(i) - 'a']--;
    }
    for (int c : counts) {
        if (c != 0) return false;
    }
    return true;
}

O(n) time and O(1) space. Sorting both strings is O(n log n). Ask what happens with Unicode input: the fixed array breaks, and a HashMap<Integer, Integer> over code points is the fix.

5. Find the second largest distinct value in an array.

Task: [5, 1, 5, 3] returns 3. An array with fewer than two distinct values has no answer.

static OptionalInt secondLargest(int[] nums) {
    Integer first = null, second = null;
    for (int n : nums) {
        if (first == null || n > first) {
            second = first;
            first = n;
        } else if (n < first && (second == null || n > second)) {
            second = n;
        }
    }
    return second == null ? OptionalInt.empty() : OptionalInt.of(second);
}

One pass, O(n). Using Integer.MIN_VALUE as a sentinel is the usual bug: it gives a wrong answer when the array actually contains that value. The n < first check skips duplicates of the maximum.

6. Return the nth Fibonacci number, and fail loudly on overflow.

Task: fib(10) returns 55. The result must never silently wrap around.

static long fib(int n) {
    if (n < 0) throw new IllegalArgumentException("n must be >= 0");
    if (n < 2) return n;
    long a = 0, b = 1;              // fib(0), fib(1)
    for (int i = 2; i <= n; i++) {
        long next = Math.addExact(a, b);
        a = b;
        b = next;
    }
    return b;
}

O(n) time and O(1) space. fib(92) is the largest Fibonacci number that fits in a long; fib(93) throws ArithmeticException instead of returning a negative number. The naive recursive version is O(2n), so ask the candidate why it is slow.

Intermediate Challenges

Mid-level rounds are mostly pattern recognition. The candidate should name the pattern before writing code, because the pattern decides the complexity.

Flowchart for picking an approach: sorted input leads to binary search or two pointers, contiguous ranges to a sliding window, fast lookups to a hash map, k smallest or largest to a heap, grids and graphs to BFS or topological sort, otherwise dynamic programming.

7. Two Sum: find two indices whose values add up to a target.

Task: [2, 7, 11, 15] with target 9 returns [0, 1]. Assume exactly one answer.

static int[] twoSum(int[] nums, int target) {
    Map<Integer, Integer> seen = new HashMap<>();
    for (int i = 0; i < nums.length; i++) {
        Integer j = seen.get(target - nums[i]);
        if (j != null) return new int[] {j, i};
        seen.put(nums[i], i);
    }
    return new int[0];
}

One pass, O(n) time and space. Checking before inserting stops an element from pairing with itself. If the array is sorted, two pointers solve it in O(1) space.

8. Check that brackets are balanced.

Task: "([]{})" returns true; "(]" and "((" return false.

static boolean isValid(String s) {
    Deque<Character> stack = new ArrayDeque<>();
    for (char c : s.toCharArray()) {
        switch (c) {
            case '(' -> stack.push(')');
            case '[' -> stack.push(']');
            case '{' -> stack.push('}');
            default -> {
                if (stack.isEmpty() || stack.pop() != c) return false;
            }
        }
    }
    return stack.isEmpty();
}

O(n). Pushing the expected closing bracket keeps the comparison to one line. A candidate who reaches for java.util.Stack is using a synchronized legacy class that extends Vector.

java.util.Stack
  • Extends Vector, so every call is synchronized
  • Also exposes index methods such as get(int)
  • Its own Javadoc points to Deque instead
ArrayDeque
  • No locking; Javadoc says it is likely faster than Stack
  • Works as a stack (push, pop) and a queue (add, poll)
  • Rejects null elements

9. Binary search a sorted array.

Task: return the index of target, or -1. [-1, 0, 3, 5, 9, 12] with 9 returns 4.

static int search(int[] nums, int target) {
    int lo = 0, hi = nums.length - 1;
    while (lo <= hi) {
        int mid = lo + (hi - lo) / 2;
        if (nums[mid] == target) return mid;
        if (nums[mid] < target) lo = mid + 1;
        else hi = mid - 1;
    }
    return -1;
}

O(log n). The classic bug is (lo + hi) / 2, which overflows once the sum passes Integer.MAX_VALUE. lo + (hi - lo) / 2 or (lo + hi) >>> 1 avoids it.

10. Group words that are anagrams of each other.

Task: ["eat", "tea", "tan", "ate", "nat", "bat"] returns three groups: eat/tea/ate, tan/nat and bat.

static List<List<String>> groupAnagrams(String[] words) {
    Map<String, List<String>> groups = new HashMap<>();
    for (String w : words) {
        char[] key = w.toCharArray();
        Arrays.sort(key);
        groups.computeIfAbsent(new String(key), k -> new ArrayList<>()).add(w);
    }
    return new ArrayList<>(groups.values());
}

O(n k log k) for n words of length k. computeIfAbsent is the line interviewers look for. A 26-count key string brings it to O(n k).

11. Find the longest substring without repeating characters.

Task: "abcabcbb" returns 3 ("abc"); "abba" returns 2.

static int longestUniqueSubstring(String s) {
    Map<Character, Integer> last = new HashMap<>();
    int best = 0;
    for (int start = 0, end = 0; end < s.length(); end++) {
        Integer prev = last.put(s.charAt(end), end);
        if (prev != null && prev >= start) start = prev + 1;
        best = Math.max(best, end - start + 1);
    }
    return best;
}

A sliding window, O(n). The prev >= start check is the trap. Without it, "abba" moves the window start backwards on the second a and returns 3.

12. Merge overlapping intervals.

Task: [[1,3], [2,6], [8,10], [15,18]] returns [[1,6], [8,10], [15,18]].

static int[][] merge(int[][] intervals) {
    Arrays.sort(intervals, Comparator.comparingInt(a -> a[0]));
    List<int[]> out = new ArrayList<>();
    for (int[] cur : intervals) {
        if (!out.isEmpty() && cur[0] <= out.getLast()[1]) {
            out.getLast()[1] = Math.max(out.getLast()[1], cur[1]);
        } else {
            out.add(cur);
        }
    }
    return out.toArray(new int[0][]);
}

O(n log n) for the sort. The Math.max handles an interval fully inside another, such as [1,10] and [2,3]. Note that this version changes the caller's arrays; ask whether that is acceptable.

13. Return the k most frequent values.

Task: [1, 1, 1, 2, 2, 3] with k = 2 returns [1, 2].

static List<Integer> topKFrequent(int[] nums, int k) {
    Map<Integer, Integer> counts = new HashMap<>();
    for (int n : nums) counts.merge(n, 1, Integer::sum);
    PriorityQueue<Map.Entry<Integer, Integer>> heap =
        new PriorityQueue<>(Map.Entry.comparingByValue());
    for (var e : counts.entrySet()) {
        heap.offer(e);
        if (heap.size() > k) heap.poll();
    }
    List<Integer> out = new ArrayList<>();
    while (!heap.isEmpty()) out.add(heap.poll().getKey());
    return out.reversed();
}

A min-heap capped at k gives O(n log k). Bucket sort by frequency is O(n) and is a good follow-up question for senior candidates.

14. Product of the array except self, without division.

Task: [1, 2, 3, 4] returns [24, 12, 8, 6].

static int[] productExceptSelf(int[] nums) {
    int n = nums.length;
    int[] out = new int[n];
    if (n == 0) return out;
    out[0] = 1;
    for (int i = 1; i < n; i++) out[i] = out[i - 1] * nums[i - 1];
    int right = 1;
    for (int i = n - 1; i >= 0; i--) {
        out[i] *= right;
        right *= nums[i];
    }
    return out;
}

The first pass stores the product of everything to the left; the second multiplies in everything to the right. O(n) time and O(1) extra space. Division fails as soon as the input contains a zero.

15. Traverse a binary tree level by level.

Task: for the tree 3, then 9 and 20, then 15 and 7, return [[3], [9, 20], [15, 7]].

static List<List<Integer>> levelOrder(TreeNode root) {
    List<List<Integer>> levels = new ArrayList<>();
    if (root == null) return levels;
    Deque<TreeNode> queue = new ArrayDeque<>();
    queue.add(root);
    while (!queue.isEmpty()) {
        int size = queue.size();
        List<Integer> level = new ArrayList<>(size);
        for (int i = 0; i < size; i++) {
            TreeNode node = queue.poll();
            level.add(node.val);
            if (node.left != null) queue.add(node.left);
            if (node.right != null) queue.add(node.right);
        }
        levels.add(level);
    }
    return levels;
}

Breadth-first search, O(n). Reading queue.size() before the inner loop is what separates the levels. The null checks are required: ArrayDeque throws on null elements.

16. Count the islands in a grid.

Task: '1' is land and '0' is water. Land cells that touch up, down, left or right form one island.

static int numIslands(char[][] grid) {
    int rows = grid.length, cols = grid[0].length, islands = 0;
    int[][] dirs = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
    for (int r = 0; r < rows; r++) {
        for (int c = 0; c < cols; c++) {
            if (grid[r][c] != '1') continue;
            islands++;
            grid[r][c] = '0';
            Deque<int[]> queue = new ArrayDeque<>();
            queue.add(new int[] {r, c});
            while (!queue.isEmpty()) {
                int[] cell = queue.poll();
                for (int[] d : dirs) {
                    int nr = cell[0] + d[0], nc = cell[1] + d[1];
                    if (nr >= 0 && nr < rows && nc >= 0 && nc < cols
                            && grid[nr][nc] == '1') {
                        grid[nr][nc] = '0';
                        queue.add(new int[] {nr, nc});
                    }
                }
            }
        }
    }
    return islands;
}

O(rows x cols). A recursive DFS is shorter, but on a 1,000 by 1,000 grid of land it can throw StackOverflowError. Marking a cell when it is queued, not when it is polled, stops it being queued twice.

Advanced Challenges

Senior rounds add data structure design, concurrency and dynamic programming. The follow-up questions matter more than the first version of the code.

17. Build an LRU cache with O(1) get and put.

Task: a cache with a fixed capacity that evicts the least recently used entry when full.

class LRUCache<K, V> extends LinkedHashMap<K, V> {
    private final int capacity;

    LRUCache(int capacity) {
        super(16, 0.75f, true);   // true = iterate in access order
        this.capacity = capacity;
    }

    @Override
    protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
        return size() > capacity;
    }
}

With capacity 2, putting 1 and 2, reading 1, then putting 3 evicts 2. Many interviewers then ask for a version built by hand from a HashMap and a linked list. They may also ask about thread safety, which this class does not have.

Grid of operation costs for ArrayList, ArrayDeque, HashMap, TreeMap and PriorityQueue: add, find a value or key, and remove the first or smallest element, each marked O(1), O(log n) or O(n).

18. Merge k sorted linked lists.

Task: merge [1,4,5], [1,3,4] and [2,6] into [1,1,2,3,4,4,5,6].

static ListNode mergeKLists(ListNode[] lists) {
    PriorityQueue<ListNode> heap =
        new PriorityQueue<>(Comparator.comparingInt(node -> node.val));
    for (ListNode head : lists) {
        if (head != null) heap.add(head);
    }
    ListNode dummy = new ListNode(0), tail = dummy;
    while (!heap.isEmpty()) {
        tail.next = heap.poll();
        tail = tail.next;
        if (tail.next != null) heap.add(tail.next);
    }
    return dummy.next;
}

O(N log k) for N nodes in k lists. The comparator trap: (a, b) -> a.val - b.val overflows for large values of opposite sign and sorts wrongly. Comparator.comparingInt never overflows.

19. Keep the running median of a stream of numbers.

Task: after adding 1 and 2 the median is 1.5; after adding 3 it is 2.0.

class MedianFinder {
    private final PriorityQueue<Integer> low = new PriorityQueue<>(Comparator.reverseOrder());
    private final PriorityQueue<Integer> high = new PriorityQueue<>();

    void addNum(int num) {
        low.offer(num);
        high.offer(low.poll());
        if (high.size() > low.size()) low.offer(high.poll());
    }

    double findMedian() {
        return low.size() > high.size()
            ? low.peek()
            : ((long) low.peek() + high.peek()) / 2.0;
    }
}

A max-heap holds the lower half and a min-heap the upper half, so addNum is O(log n) and findMedian is O(1). The (long) cast stops two large values overflowing when they are added.

20. Find the fewest coins that make an amount.

Task: coins [1, 2, 5] and amount 11 return 3 (5 + 5 + 1). If no combination works, return -1.

static int coinChange(int[] coins, int amount) {
    int[] dp = new int[amount + 1];
    Arrays.fill(dp, amount + 1);          // stands in for infinity
    dp[0] = 0;
    for (int a = 1; a <= amount; a++) {
        for (int coin : coins) {
            if (coin <= a) dp[a] = Math.min(dp[a], dp[a - coin] + 1);
        }
    }
    return dp[amount] > amount ? -1 : dp[amount];
}

O(amount x coins). Greedy fails here. With coins [1, 3, 4] and amount 6, greedy picks 4 + 1 + 1 while the answer is 3 + 3. Using amount + 1 instead of Integer.MAX_VALUE avoids overflow on + 1.

21. Can a string be split into dictionary words?

Task: "leetcode" with ["leet", "code"] returns true; "catsandog" with ["cats", "dog", "sand", "and", "cat"] returns false.

static boolean wordBreak(String s, List<String> dict) {
    Set<String> words = new HashSet<>(dict);
    boolean[] ok = new boolean[s.length() + 1];
    ok[0] = true;
    for (int end = 1; end <= s.length(); end++) {
        for (int start = 0; start < end; start++) {
            if (ok[start] && words.contains(s.substring(start, end))) {
                ok[end] = true;
                break;
            }
        }
    }
    return ok[s.length()];
}

ok[i] means the first i characters can be split. That is O(n2) checks, each with a substring copy. Limiting start to the longest word length is the usual improvement.

22. How much rain water is trapped between the bars?

Task: heights [0,1,0,2,1,0,1,3,2,1,2,1] trap 6 units.

static int trap(int[] height) {
    int left = 0, right = height.length - 1;
    int leftMax = 0, rightMax = 0, water = 0;
    while (left < right) {
        if (height[left] < height[right]) {
            leftMax = Math.max(leftMax, height[left]);
            water += leftMax - height[left];
            left++;
        } else {
            rightMax = Math.max(rightMax, height[right]);
            water += rightMax - height[right];
            right--;
        }
    }
    return water;
}

O(n) time and O(1) space. The pointer on the lower side moves because its water level is limited by its own side's maximum. Precomputed left and right maximum arrays are an easier O(n) space version to explain first.

23. Order tasks so every prerequisite comes first.

Task: 4 courses and pairs {course, prerequisite} of {1,0}, {2,0}, {3,1}, {3,2} return [0, 1, 2, 3]. Return an empty list if there is a cycle.

static List<Integer> buildOrder(int n, int[][] deps) {
    List<List<Integer>> next = new ArrayList<>();
    for (int i = 0; i < n; i++) next.add(new ArrayList<>());
    int[] indegree = new int[n];
    for (int[] d : deps) {
        next.get(d[1]).add(d[0]);
        indegree[d[0]]++;
    }
    Deque<Integer> ready = new ArrayDeque<>();
    for (int i = 0; i < n; i++) {
        if (indegree[i] == 0) ready.add(i);
    }
    List<Integer> order = new ArrayList<>();
    while (!ready.isEmpty()) {
        int c = ready.poll();
        order.add(c);
        for (int m : next.get(c)) {
            if (--indegree[m] == 0) ready.add(m);
        }
    }
    return order.size() == n ? order : List.of();
}

Kahn's algorithm, O(V + E). If some nodes never reach in-degree zero, they sit on a cycle, which is how the size check detects it. Build systems and database migration tools solve this same problem.

24. Run 10,000 blocking tasks at once and sum their results.

Task: each task blocks for one second, as a network call would. All of them together should finish in about one second, not 10,000.

static long runAll(int tasks) throws Exception {
    try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
        List<Future<Integer>> futures = new ArrayList<>();
        for (int i = 0; i < tasks; i++) {
            int id = i;
            futures.add(executor.submit(() -> {
                Thread.sleep(Duration.ofSeconds(1));   // stands in for I/O
                return id;
            }));
        }
        long sum = 0;
        for (Future<Integer> f : futures) sum += f.get();
        return sum;
    }
}

Virtual threads, final in JDK 21 (JEP 444), are cheap enough to create one per task, so there is no pool to size. They help blocking I/O, not CPU-bound work.

Before JDK 24, blocking inside synchronized pinned the carrier thread; JEP 491 removed nearly all of those cases.

25. Evaluate an expression tree with records and pattern matching.

Task: model numbers, addition, multiplication and negation, then evaluate 2 + 3 * 4 to 14.

sealed interface Expr permits Num, Add, Mul, Neg {}
record Num(int value) implements Expr {}
record Add(Expr left, Expr right) implements Expr {}
record Mul(Expr left, Expr right) implements Expr {}
record Neg(Expr operand) implements Expr {}

static int eval(Expr e) {
    return switch (e) {
        case Num(int v) -> v;
        case Add(Expr l, Expr r) -> eval(l) + eval(r);
        case Mul(Expr l, Expr r) -> eval(l) * eval(r);
        case Neg(Expr x) -> -eval(x);
    };
}

// eval(new Add(new Num(2), new Mul(new Num(3), new Num(4)))) returns 14

Record patterns (JEP 440) and pattern matching for switch (JEP 441) are both final since JDK 21. Because Expr is sealed, the switch needs no default. Add a new record type and this method fails to compile until it handles it.

That compile error is the point of the design.

What Changed Recently

These challenges use features that became final between 2024 and 2026. A candidate targeting JDK 25 should know all of them.

Mar 19, 2024
JDK 22: unnamed variables and patterns (_)
Mar 18, 2025
JDK 24: stream gatherers; virtual threads stop pinning in synchronized
Sep 16, 2025
JDK 25 (LTS): compact source files, flexible constructor bodies, scoped values
Mar 17, 2026
JDK 26: HTTP/3 for the HTTP Client, Applet API removed
Sep 15, 2026
JDK 27: G1 default everywhere, compact object headers by default

26. Handle events with a switch that ignores the fields it does not use.

Task: describe clicks by button, key presses by key, and every scroll the same way. Also sum a list of strings, skipping the ones that are not numbers.

sealed interface Event permits Click, KeyPress, Scroll {}
record Click(int x, int y, int button) implements Event {}
record KeyPress(char key, boolean shift) implements Event {}
record Scroll(int delta) implements Event {}

static String describe(Event e) {
    return switch (e) {
        case Click(_, _, int button) -> "click " + button;
        case KeyPress(char key, _) -> "key " + key;
        case Scroll _ -> "scroll";
    };
}

static int sumValid(List<String> raw) {
    int sum = 0;
    for (String s : raw) {
        try {
            sum += Integer.parseInt(s);
        } catch (NumberFormatException _) {
            // not a number: skip it
        }
    }
    return sum;
}

The underscore marks a variable or pattern that is never read. It became final in JDK 22 with JEP 456. It works in patterns, catch blocks, lambda parameters and loop variables. A reviewer can see at once that the value is unused on purpose.

27. Compute a moving average with a stream gatherer.

Task: prices [10, 20, 30, 40] with a window of 3 return [20.0, 30.0].

static List<Double> movingAverage(List<Integer> prices, int window) {
    return prices.stream()
        .gather(Gatherers.windowSliding(window))
        .map(w -> w.stream().mapToInt(Integer::intValue).average().orElseThrow())
        .toList();
}

// Running total with another built-in gatherer: [1, 3, 6, 10]
List<Integer> totals = Stream.of(1, 2, 3, 4)
    .gather(Gatherers.scan(() -> 0, Integer::sum))
    .toList();

Stream::gather adds custom intermediate operations, final in JDK 24 with JEP 485. The built-ins are fold, mapConcurrent, scan, windowFixed and windowSliding.

Ask about the edge case of a list shorter than the window. windowSliding then emits one smaller window with every element, so the method returns one average, not none.

28. Pass a request ID to deep code without adding a parameter everywhere.

Task: a logger several calls deep must print the current request's ID. It must be safe with millions of virtual threads.

static final ScopedValue<String> REQUEST_ID = ScopedValue.newInstance();

static void handle(String requestId) {
    ScopedValue.where(REQUEST_ID, requestId).run(() -> loadOrder());
}

static void loadOrder() {
    log("loading order");
}

static void log(String msg) {
    System.out.println("[" + REQUEST_ID.orElse("none") + "] " + msg);
}

// handle("req-42") prints: [req-42] loading order

Scoped values became final in JDK 25 with JEP 506. Unlike a ThreadLocal, callees cannot change the binding. It ends when run returns, so there is no remove() to forget. One JDK 25 change from the previews: orElse no longer accepts null.

29. Write a complete word-count program in the fewest lines Java allows.

Task: read a line from the console and print how often each word appears, in alphabetical order.

void main() {
    String line = IO.readln("Enter a sentence: ");
    Map<String, Integer> counts = new TreeMap<>();
    for (String w : line.toLowerCase().split("\\s+")) {
        counts.merge(w, 1, Integer::sum);
    }
    IO.println(counts);
}

// java WordCount.java, input "the cat and the hat"
// prints {and=1, cat=1, hat=1, the=2}

Compact source files and instance main methods are final in JDK 25 (JEP 512). There is no class declaration and no import. A compact source file imports the whole java.base module, which is why Map and TreeMap resolve.

IO is a new class in java.lang.

30. Validate a constructor argument before calling super().

Task: an Employee must reject a blank office ID before the Person constructor runs.

class Person {
    final String name;

    Person(String name) {
        this.name = name;
    }
}

class Employee extends Person {
    final String officeId;

    Employee(String name, String officeId) {
        if (officeId == null || officeId.isBlank()) {
            throw new IllegalArgumentException("officeId is required");
        }
        this.officeId = officeId;   // allowed before super() since JDK 25
        super(name);
    }
}

Flexible constructor bodies are final in JDK 25 (JEP 513). Code before super(...) may check arguments and assign the class's own fields. It may not read fields or call instance methods on the new object.

Before JDK 25 the check usually hid inside a static helper passed as an argument to super.

Which JDK to allow. Challenges 28 to 32 need JDK 22, 24 or 25. Tell candidates which version the environment runs, since many online editors still default to JDK 17 or 21.

Signs of a Strong Answer

  • They name the pattern (sliding window, heap, BFS) and its complexity before they type.
  • They pick ArrayDeque for stacks and queues and can say why Stack and LinkedList are worse.
  • They spot integer overflow unprompted: the midpoint in binary search, a.val - b.val comparators, fib(93).
  • They use Map.merge, computeIfAbsent and Comparator.comparingInt instead of hand-written null checks.
  • They model closed sets of types with sealed interfaces and records, and let the compiler check the switch.
  • They know virtual threads help blocking I/O, not CPU-bound loops.

Hiring Java Developers

Second Talent places pre-vetted Java developers from Asia, screened with live coding challenges like these. Our Java developer cost guide shows typical rates by country.

Tell us the stack and we send a shortlist within 24 hours. Start hiring, or see our Spring Boot and Kotlin interview guides.

Hiring developers in Southeast Asia?

Get Cost Guide

How would you like to talk?

WhatsApp us Prefer texting at your own pace? Just hit us up on WhatsApp. We promise no spam and a hassle-free experience.

Loading available times…