Course contents

Stream coding problems

Twelve classic Java stream coding problems, grouped by collector and operation choice, with interview follow-ups and trade-offs.

PremiumMust know for interviewsPractice problems17 min readCoreReviewed Oct 2026 · Java 21
On this page
  1. Picture it first: choose the family before the method
  2. Warm-up problems: ranking, counting and first unique values
  3. Problem 1: second-highest salary
  4. Problem 2: frequency of each word, sorted by count desc then alphabetically
  5. Problem 3: first non-repeating character
  6. Core collector problems: groups, duplicates and top totals
  7. Problem 4: group employees by department with average salary
  8. Problem 5: find duplicates in a list
  9. Problem 6: top 3 customers by total order value
  10. Reshaping problems: flatten, partition and choose a best value
  11. Problem 7: flatten orders into distinct product names sorted alphabetically
  12. Problem 8: partition numbers into primes and non-primes
  13. Problem 9: longest string per group
  14. Harder problems: when streams start to creak
  15. Problem 10: merge two sorted lists via streams, and why a loop is better
  16. Problem 11: department with the highest headcount
  17. Problem 12: running total, and why streams are awkward
  18. Advanced patterns and interview trade-offs
  19. How to say it in the interview

🧩 What are stream coding problems?

Stream coding problems are small interview tasks where the hard part is choosing the right stream shape, not memorising a method name. They act like a set of keys: groupingBy opens bucket problems, flatMap opens nested collections, and sorted plus limit opens ranking. A good answer says the pattern out loud before writing code.

For this lesson, we'll focus on 12 classic Java 21 stream problems, ordered from easy to hard. I will keep each solution compact, but I will also call out when a stream is not the best production choice. Interviewers ask these because they reveal whether you understand encounter order, downstream collectors, tie-breakers and the cost of sorting.

Below the line (out of scope)

The 30-second answer

I start by naming the problem family: rank, count, group, flatten, partition or scan. Ranking usually means sorted, distinct, skip or limit; counting and totals usually mean groupingBy with counting, summingInt or averagingInt; nested collections usually need flatMap; boolean splits use partitioningBy. If the task needs a running total or a true merge of two already sorted lists, I mention that a loop is clearer and often O(n) instead of sorting everything again. The stream solution can still be useful in an interview, but the trade-off has to be part of the answer.

What you’ll learn

  • Solve salary, frequency and first-unique problems with distinct, groupingBy, counting and stable ordering
  • Use groupingBy downstream collectors for averages, totals, max values and headcounts
  • Reach for flatMap, distinct, sorted and partitioningBy when the problem shape calls for them
  • Explain when a stream solution is readable and when a plain loop is the better interview answer
  • Handle tie-breakers, empty inputs, duplicate keys and encounter-order requirements without hand-waving
  • Talk through IntStream.range recipes for index-aware work, including why running totals are awkward in streams

01Picture it first: choose the family before the method

Think of the prompt as a package on a sorting belt. If the package asks for "per department", it goes to the grouping lane. If it asks for "top 3", it goes to the ranking lane. If it asks for "all products inside all orders", it goes to the flattening lane.

That first lane choice matters more than the exact syntax. Most wrong stream answers start from a favorite method instead of the problem shape.

Fig. 1

A map from stream interview problem families to the collectors and operations that usually solve them

A map from stream interview problem families to the collectors and operations that usually solve them

Notice the split. Problems 1, 2, 6 and 11 care about ranking or aggregation. Problems 7 and 8 are shape changes. Problems 3 and 12 care about encounter order, where streams need extra care.

02Warm-up problems: ranking, counting and first unique values

These first three are common because they fit on a whiteboard and still expose real edges: duplicates, deterministic ordering and empty input.

03Problem 1: second-highest salary

Interviewer prompt: "Given a list of employees, return the second-highest distinct salary. If there is no second salary, fail clearly."

Try it yourself first

Before reading on, decide whether two employees with the same highest salary should count as first and second, or as one distinct salary.

import java.util.Comparator;
import java.util.List;

final class Problem1SecondHighestSalary {
    record Employee(String name, String department, int salary) { }

    public static void main(String[] args) {
        List<Employee> employees = List.of(
                new Employee("Mina", "Platform", 160_000),
                new Employee("Omar", "Data", 150_000),
                new Employee("Ravi", "Platform", 160_000),
                new Employee("Iris", "Support", 120_000)
        );

        int secondHighest = employees.stream()
                .map(Employee::salary)
                .distinct()
                .sorted(Comparator.reverseOrder())
                .skip(1)
                .findFirst()
                .orElseThrow();

        System.out.println(secondHighest); // 150000
    }
}

Why this works: map narrows the stream to salaries, distinct removes duplicate salary values, and skip(1) walks past the highest distinct salary. The sort is O(n log n), which is fine for interview-sized input and easy to read. For millions of salaries, I would use a one-pass loop that tracks the best two distinct values.

What interviewers follow up with: "Can you do it without sorting?" Say yes, then keep highest and secondHighest variables while rejecting duplicate salaries.

04Problem 2: frequency of each word, sorted by count desc then alphabetically

Interviewer prompt: "Given a sentence, count each word case-insensitively and print the result sorted by highest count, then alphabetically for ties."

Try it yourself first

Before reading the code, predict which order failed and payment should appear in when both have count 2.

import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.Function;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

final class Problem2WordFrequency {
    public static void main(String[] args) {
        String text = "Payment failed payment retried invoice failed";

        Map<String, Long> counts = Pattern.compile("\\W+").splitAsStream(text.toLowerCase())
                .filter(word -> !word.isBlank())
                .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
                .entrySet().stream()
                .sorted(Map.Entry.<String, Long>comparingByValue(Comparator.reverseOrder())
                        .thenComparing(Map.Entry.comparingByKey()))
                .collect(Collectors.toMap(
                        Map.Entry::getKey,
                        Map.Entry::getValue,
                        (left, right) -> left,
                        LinkedHashMap::new
                ));

        System.out.println(counts); // {failed=2, payment=2, invoice=1, retried=1}
    }
}

Why this works: groupingBy creates the frequency map, then the entry stream sorts by two rules. LinkedHashMap::new matters because the final map needs to remember the sorted iteration order. A plain HashMap would throw away the presentation order you just paid to compute.

What interviewers follow up with: "What about punctuation and Unicode?" I usually say this regex is enough for the exercise, but production tokenization needs a clearer language rule.

05Problem 3: first non-repeating character

Interviewer prompt: "Given a string, return the first character that appears exactly once. Preserve the original order."

Try it yourself first

Before reading on, answer for swiss. Then explain why a regular HashMap is risky for this problem.

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

final class Problem3FirstNonRepeatingCharacter {
    public static void main(String[] args) {
        String value = "swiss";

        Map<Integer, Long> counts = value.codePoints().boxed()
                .collect(Collectors.groupingBy(
                        Function.identity(),
                        LinkedHashMap::new,
                        Collectors.counting()
                ));

        String first = counts.entrySet().stream()
                .filter(entry -> entry.getValue() == 1)
                .map(entry -> new String(Character.toChars(entry.getKey())))
                .findFirst()
                .orElseThrow();

        System.out.println(first); // w
    }
}

Why this works: LinkedHashMap keeps first-seen order, so the second stream sees characters in the same order as the input. I used codePoints() instead of chars() because it handles supplementary Unicode code points better than raw UTF-16 units. For ASCII interview input, chars() is usually accepted.

What interviewers follow up with: "Can you do it in one pass?" With a LinkedHashMap<Integer, Integer> and manual counts, yes. Pure streams make the order-preserving count readable, but not one-pass.

06Core collector problems: groups, duplicates and top totals

Now the prompts become more domain-shaped. This is where downstream collectors start doing the real work, which we cover in more depth in groupingBy and partitioningBy in depth.

07Problem 4: group employees by department with average salary

Interviewer prompt: "Given employees with department and salary, return the average salary per department. Make the output deterministic."

Try it yourself first

Before reading the code, name the classifier and the downstream collector separately.

import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;

final class Problem4DepartmentAverageSalary {
    record Employee(String name, String department, int salary) { }

    public static void main(String[] args) {
        List<Employee> employees = List.of(
                new Employee("Asha", "Platform", 120_000),
                new Employee("Ben", "Platform", 150_000),
                new Employee("Chen", "Sales", 90_000),
                new Employee("Diya", "Sales", 110_000)
        );

        Map<String, Double> averageByDepartment = employees.stream()
                .collect(Collectors.groupingBy(
                        Employee::department,
                        TreeMap::new,
                        Collectors.averagingInt(Employee::salary)
                ));

        System.out.println(averageByDepartment); // {Platform=135000.0, Sales=100000.0}
    }
}

Why this works: Employee::department chooses the bucket, averagingInt decides the value inside each bucket, and TreeMap::new sorts department names. The result type is Double, so talk about rounding if money output is part of the requirement.

What interviewers follow up with: "Can you also return count and average?" Use summarizingInt or teeing, depending on the shape they want.

08Problem 5: find duplicates in a list

Interviewer prompt: "Given a list of order IDs, return each ID that appears more than once, in the order it first becomes a duplicate."

Try it yourself first

Before reading on, decide what should happen to the third copy of the same ID. Should it appear again?

import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

final class Problem5FindDuplicates {
    public static void main(String[] args) {
        List<String> ids = List.of("o-7", "o-1", "o-7", "o-2", "o-1", "o-9");

        Set<String> seen = new HashSet<>();
        Set<String> duplicates = ids.stream()
                .filter(id -> !seen.add(id))
                .collect(Collectors.toCollection(LinkedHashSet::new));

        System.out.println(duplicates); // [o-7, o-1]
    }
}

Why this works: Set.add returns false when the ID was already present, so the filter keeps only repeated IDs. The final LinkedHashSet prevents a third copy from appearing again. This is a stateful predicate, so keep it sequential and local to one pipeline.

What interviewers follow up with: "Is this safe in parallel?" No. Use a frequency map with groupingBy(..., counting()) if you need a more collector-shaped answer.

09Problem 6: top 3 customers by total order value

Interviewer prompt: "Given orders with customer IDs and amounts, return the top 3 customers by total spend. Break ties by customer ID."

Try it yourself first

Before reading the solution, say why sorting individual orders first is not enough.

import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

final class Problem6TopCustomersByOrderValue {
    record Order(String customerId, int cents) { }

    public static void main(String[] args) {
        List<Order> orders = List.of(
                new Order("c-1", 4000),
                new Order("c-2", 7000),
                new Order("c-1", 2500),
                new Order("c-3", 9000),
                new Order("c-4", 1000),
                new Order("c-2", 1500)
        );

        List<Map.Entry<String, Integer>> topCustomers = orders.stream()
                .collect(Collectors.groupingBy(
                        Order::customerId,
                        Collectors.summingInt(Order::cents)
                ))
                .entrySet().stream()
                .sorted(Map.Entry.<String, Integer>comparingByValue(Comparator.reverseOrder())
                        .thenComparing(Map.Entry.comparingByKey()))
                .limit(3)
                .toList();

        System.out.println(topCustomers); // [c-3=9000, c-2=8500, c-1=6500]
    }
}

Premium lesson

Keep reading with the Java Pass

The rest of this lesson is part of Java One-Stop Interview Prep: in-depth explanations, diagrams, model answers and how to say them in the interview.

  • Reshaping problems: flatten, partition and choose a best value
  • Problem 7: flatten orders into distinct product names sorted alphabetically
  • Problem 8: partition numbers into primes and non-primes
  • Problem 9: longest string per group
  • Harder problems: when streams start to creak
  • Problem 10: merge two sorted lists via streams, and why a loop is better
  • Problem 11: department with the highest headcount
  • Problem 12: running total, and why streams are awkward
  • Advanced patterns and interview trade-offs
  • How to say it in the interview

From ₹199 · one-time payment · No auto-renewal

Practise this topicStream coding problems: interview questions & scenarios
Further reading (6)