Java Stream API Coding Interview
Questions - Intermediate Level (31–70)
31) Group a list of strings by their length
Approach 1 — groupingBy (common):
List<String> words = [Link]("apple","banana","kiwi","pear");
Map<Integer, List<String>> byLen = [Link]()
.collect([Link](String::length));
Approach 2 — with downstream toSet (unique values):
Map<Integer, Set<String>> byLenSet = [Link]()
.collect([Link](String::length,
[Link]()));
Explanation: [Link](classifier) buckets elements by key;
downstream collectors let you control value type.
32) Count the frequency of each element in a list
Approach 1 — groupingBy + counting (idiomatic):
List<String> items = [Link]("a","b","a","c","b","a");
Map<String, Long> freq = [Link]()
.collect([Link]([Link](),
[Link]()));
Approach 2 — toMap with merge function:
Map<String, Integer> freq2 = [Link]()
.collect([Link]([Link](), v -> 1,
Integer::sum));
Explanation: Both build frequency maps; groupingBy easily returns Long counts.
33) Find the longest string in a list
Approach 1 — max with comparator:
Optional<String> longest = [Link]()
.max([Link](String::length));
Approach 2 — reduce pairwise:
Optional<String> longest2 = [Link]()
.reduce((a, b) -> [Link]() >= [Link]() ? a : b);
Explanation: max is clearer; reduce demonstrates functional reasoning.
34) Find the shortest string in a list
Approach 1 — min with comparator:
Optional<String> shortest = [Link]()
.min([Link](String::length));
Approach 2 — sort and take first (less efficient):
Optional<String> shortest2 = [Link]()
.sorted([Link](String::length))
.findFirst();
Explanation: min is O(n); sorting is O(n log n) but sometimes simpler.
35) Implement pagination using skip() and limit()
Approach 1 — slice a list:
int page = 3, size = 10;
List<T> pageData = [Link]()
.skip((long)(page - 1) * size)
.limit(size)
.collect([Link]());
Approach 2 — using IntStream index slicing:
List<T> pageData2 = [Link](0, [Link]())
.filter(i -> i >= (page-1)*size && i < page*size)
.mapToObj(list::get)
.collect([Link]());
Explanation: skip/limit is straightforward; index-based approach helps when
computing offsets with boundaries.
36) Sort a list of objects by a field
Approach 1 — [Link]:
List<Employee> sorted = [Link]()
.sorted([Link](Employee::getName))
.collect([Link]());
Approach 2 — lambda comparator:
List<Employee> sorted2 = [Link]()
.sorted((a,b) -> [Link]().compareTo([Link]()))
.toList();
Explanation: [Link] is more readable and null-safe variants are
available.
37) Sort a list of objects by multiple fields
Approach 1 — thenComparing:
[Link]()
.sorted([Link](Employee::getDept)
.thenComparing(Employee::getName))
.toList();
Approach 2 — combined lambda comparator:
[Link]()
.sorted((a,b) -> {
int r = [Link]().compareTo([Link]());
return r != 0 ? r : [Link]().compareTo([Link]());
})
.toList();
Explanation: Chain comparators with thenComparing for clarity; handle nulls with
[Link]/Last.
38) Find the second-highest salary from a list of employees
Approach 1 — map, distinct, sort desc, skip:
Optional<Integer> second = [Link]()
.map(Employee::getSalary)
.distinct()
.sorted([Link]())
.skip(1)
.findFirst();
Approach 2 — use TreeSet (unique sorted set):
TreeSet<Integer> set = [Link]()
.map(Employee::getSalary)
.collect([Link](() -> new
TreeSet<>([Link]())));
Integer second2 = [Link]().skip(1).findFirst().orElse(null);
Explanation: Distinct + reverse sort is concise; TreeSet avoids additional sorting on
repeated queries.
39) Find the top N elements from a list
Approach 1 — sort and limit:
int N = 5;
List<Integer> topN = [Link]()
.sorted([Link]())
.limit(N)
.toList();
Approach 2 — maintain a min-heap (efficient for large streams):
PriorityQueue<Integer> pq = new PriorityQueue<>();
[Link](x -> { [Link](x); if ([Link]() > N) [Link](); });
List<Integer> top =
[Link]().sorted([Link]()).toList();
Explanation: Sorting is easy; heap approach is O(n log N) and better for streaming large
inputs.
40) Merge two lists using streams
Approach 1 — [Link]:
List<T> merged = [Link]([Link](),
[Link]()).collect([Link]());
Approach 2 — [Link] + flatMap (extendable to many lists):
List<T> merged2 = [Link](a, b)
.flatMap(Collection::stream)
.toList();
Explanation: concat merges two streams; flatMap generalizes to combine many
collections.
41) Flatten a list of arrays into a single list
Approach 1 — flatMap with Arrays::stream:
List<String> flat = [Link]()
.flatMap(Arrays::stream)
.collect([Link]());
Approach 2 — primitive arrays with flatMapToInt:
int[] flat = [Link]()
.flatMapToInt(Arrays::stream)
.toArray();
Explanation: Use flatMap for object streams; primitive flatMapToX for performance.
42) Remove duplicates based on a specific field in objects
Approach 1 — toMap keyed by field (keeps first):
Collection<Employee> unique = [Link]()
.collect([Link](Employee::getId, e -> e, (e1,e2) -> e1,
LinkedHashMap::new))
.values();
Approach 2 — TreeSet with comparator on field (keeps unique per comparator):
List<Employee> unique2 = [Link]()
.collect([Link](
[Link](() -> new
TreeSet<>([Link](Employee::getId))),
ArrayList::new));
Explanation: toMap dedupes by key; TreeSet enforces ordering and uniqueness via
comparator.
43) Count words in a string using streams
Approach 1 — split + groupingBy:
Map<String, Long> counts = [Link]([Link]("\\s+"))
.map(String::toLowerCase)
.collect([Link]([Link](),
[Link]()));
Approach 2 — regex [Link]() (Unicode-aware, Java 9+):
Pattern p = [Link]("\\p{L}+");
Map<String, Long> counts2 = [Link](text).results()
.map(m -> [Link]().toLowerCase())
.collect([Link]([Link](),
[Link]()));
Explanation: Splitting is simple; regex captures word tokens more robustly (punctuation
etc).
44) Reverse a list using streams
Approach 1 — index-based mapping:
List<T> reversed = [Link](0, [Link]())
.mapToObj(i -> [Link]([Link]() - 1 - i))
.collect([Link]());
Approach 2 — non-stream [Link] (recommended for mutability):
List<T> copy = new ArrayList<>(list);
[Link](copy);
Explanation: Streams can reverse by index; for in-place reversal [Link]
is simpler and faster.
45) Create an infinite stream of numbers and limit the output
Approach 1 — [Link]:
List<Integer> first10 = [Link](0, n -> n +
1).limit(10).collect([Link]());
Approach 2 — primitive [Link]:
int[] first10 = [Link](0, n -> n + 1).limit(10).toArray();
Explanation: iterate generates an infinite sequence; always use limit to bound it.
46) Map a list of numbers to their cubes
Approach 1 — object stream, compute cube:
List<Integer> cubes = [Link]().map(n -> n * n * n).toList();
Approach 2 — primitive stream for performance:
int[] cubes = [Link]().mapToInt(n -> n * n * n).toArray();
Explanation: Primitive streams avoid boxing and are faster for numeric transformations.
47) Get the frequency of each word from a paragraph
Approach 1 — split + grouping:
Map<String, Long> freq =
[Link]([Link]().split("\\W+"))
.filter(s -> ![Link]())
.collect([Link]([Link](),
[Link]()));
Approach 2 — regex tokenization with Pattern:
Pattern p = [Link]("\\p{L}+");
Map<String, Long> freq2 = [Link](para).results()
.map(m -> [Link]().toLowerCase())
.collect([Link]([Link](),
[Link]()));
Explanation: Similar to Q43; prefer regex for robust token extraction.
48) Filter employees earning more than a certain salary
Approach 1 — simple filter:
List<Employee> rich = [Link]()
.filter(e -> [Link]() > threshold)
.toList();
Approach 2 — [Link] downstream (Java 9+):
List<Employee> rich2 = [Link]()
.collect([Link](e -> [Link]() > threshold,
[Link]()));
Explanation: Both yield same result; filtering can be handy inside larger collectors
(e.g., grouping).
49) Find the oldest person in a list of people
Approach 1 — max by age:
Optional<Person> oldest = [Link]()
.max([Link](Person::getAge));
Approach 2 — reduce to keep older:
Optional<Person> oldest2 = [Link]()
.reduce((p1, p2) -> [Link]() >= [Link]() ? p1 : p2);
Explanation: max is idiomatic; reduce shows manual fold logic.
50) Find the youngest person in a list of people
Approach 1 — min by age:
Optional<Person> youngest = [Link]()
.min([Link](Person::getAge));
Approach 2 — sorted then first (less efficient):
Optional<Person> youngest2 = [Link]()
.sorted([Link](Person::getAge))
.findFirst();
Explanation: min is O(n), sorting is O(n log n).
51) Group employees by department
Approach 1 — groupingBy to list:
Map<String, List<Employee>> byDept = [Link]()
.collect([Link](Employee::getDepartment));
Approach 2 — map dept -> set of names:
Map<String, Set<String>> namesByDept = [Link]()
.collect([Link](Employee::getDepartment,
[Link](Employee::getName, [Link]())));
Explanation: Downstream mapping extracts and collects only the desired property.
52) Group employees by department and count them
Approach 1 — groupingBy + counting:
Map<String, Long> counts = [Link]()
.collect([Link](Employee::getDepartment,
[Link]()));
Approach 2 — toMap accumulation:
Map<String, Integer> counts2 = [Link]()
.collect([Link](Employee::getDepartment, e -> 1,
Integer::sum));
Explanation: groupingBy is clear; toMap provides integer counts when preferred.
53) Find duplicate elements in a list
Approach 1 — build frequency map then filter:
Set<T> duplicates = [Link]()
.collect([Link]([Link](),
[Link]()))
.entrySet().stream()
.filter(e -> [Link]() > 1)
.map([Link]::getKey)
.collect([Link]());
Approach 2 — single-pass Set tracking (non-stream but O(n)):
Set<T> seen = new HashSet<>(), dups = new HashSet<>();
for (T x : list) if () [Link](x);
Explanation: Frequency map is functional; set tracking is efficient and simple.
54) Remove duplicates while maintaining order
Approach 1 — distinct() (preserves encounter order for ordered streams):
List<T> unique = [Link]().distinct().toList();
Approach 2 — LinkedHashSet roundtrip (non-stream):
List<T> unique2 = new ArrayList<>(new LinkedHashSet<>(list));
Explanation: distinct() uses encounter order of source; LinkedHashSet preserves
insertion order.
55) Find the element with the maximum frequency in a list
Approach 1 — frequency map then max entry:
String mode = [Link]()
.collect([Link]([Link](),
[Link]()))
.entrySet().stream()
.max([Link]())
.map([Link]::getKey)
.orElse(null);
Approach 2 — manual merge then max:
Map<String, Long> m = new HashMap<>();
[Link](s -> [Link](s, 1L, Long::sum));
String mode2 =
[Link]().stream().max([Link]()).map([Link]::
getKey).orElse(null);
Explanation: Build counts then pick max; ties resolved by map ordering unless otherwise
specified.
56) Get the last element of a list using streams
Approach 1 — reduce to last:
T last = [Link]().reduce((a, b) -> b).orElse(null);
Approach 2 — index-based stream:
T last2 = [Link](0, [Link]())
.mapToObj(list::get)
.skip([Link]() - 1)
.findFirst()
.orElse(null);
Explanation: reduce((a,b)->b) returns the last observed element; index approach is
explicit.
57) Check if a list is sorted
Approach 1 — pairwise index check:
boolean sorted = [Link](1, [Link]())
.allMatch(i -> [Link](i-1).compareTo([Link](i)) <= 0);
Approach 2 — compare with sorted copy:
boolean sorted2 = [Link]().sorted().toList().equals(list);
Explanation: Pairwise check is O(n); comparing with sorted copy is simpler but O(n log
n).
58) Convert a list of integers to a formatted string
Approach 1 — map + joining:
String s =
[Link]().map(String::valueOf).collect([Link](", "));
Approach 2 — template formatting per element:
String s2 = [Link]().map(n -> [Link]("[%d]",
n)).collect([Link](" "));
Explanation: joining easily formats sequences; [Link] for custom templates.
59) Get a list of prime numbers from a range
Approach 1 — trial division up to sqrt(n):
List<Integer> primes = [Link](2, end)
.filter(n -> [Link](2,
(int)[Link](n)).noneMatch(d -> n % d == 0))
.boxed().toList();
Approach 2 — simpler (but slower) check up to n-1:
List<Integer> primes2 = [Link](2, end)
.filter(n -> [Link](2, n-1).allMatch(d -> n % d !=
0))
.boxed().toList();
Explanation: Use sqrt(n) bound for efficiency.
60) Generate Fibonacci numbers using streams
Approach 1 — pair tuple with iterate:
List<Integer> fib = [Link](new int[]{0,1}, a -> new int[]{a[1],
a[0]+a[1]})
.limit(n)
.map(a -> a[0])
.toList();
Approach 2 — using Long tuple for larger sequences:
List<Long> fib2 = [Link](new long[]{0,1}, a -> new long[]{a[1],
a[0]+a[1]})
.limit(n)
.map(a -> a[0])
.toList();
Explanation: Use two-value state in iterate to carry previous and current.
61) Implement custom collectors (example: sum of squares)
Approach 1 — [Link]:
Collector<Integer, int[], Integer> sumSquares = [Link](
() -> new int[1],
(a, t) -> a[0] += t * t,
(a, b) -> { a[0] += b[0]; return a; },
a -> a[0]
);
int res = [Link](1,2,3).collect(sumSquares);
Approach 2 — use mapToInt + sum (simpler):
int res2 = [Link](1,2,3).mapToInt(x -> x * x).sum();
Explanation: Custom collectors give control; often primitive streams give simpler/faster
solutions.
62) Use reduce() to concatenate strings
Approach 1 — reduce with identity (less efficient due to repeated concatenation):
String s = [Link]().reduce("", (a,b)-> [Link]() ? b : a + " "
+ b);
Approach 2 — [Link] (preferred):
String s2 = [Link]().collect([Link](" "));
Explanation: joining uses efficient StringBuilder internally; avoid naive reduce for
many strings.
63) Use reduce() to multiply all numbers in a list
Approach 1 — reduce with identity 1:
int product = [Link]().reduce(1, (a,b) -> a * b);
Approach 2 — mapToLong and reduce to long to reduce overflow risk:
long product2 = [Link]().mapToLong(Integer::longValue).reduce(1L,
(a,b) -> a * b);
Explanation: Choose numeric type carefully to avoid overflow.
64) Use reduce() to find the max number without max()
Approach 1 — reduce(Integer::max):
Optional<Integer> max = [Link]().reduce(Integer::max);
Approach 2 — reduce with identity and ternary comparator:
int max2 = [Link]().reduce(Integer.MIN_VALUE, (a,b) -> a > b ? a :
b);
Explanation: Both implement fold to largest element; ensure correct identity for empty
lists.
65) Convert a map’s values to a list using streams
Approach 1 — stream over values():
List<V> vals = [Link]().stream().collect([Link]());
Approach 2 — stream entries then map to value:
List<V> vals2 =
[Link]().stream().map([Link]::getValue).toList();
Explanation: Both obtain values; entrySet useful if you also need keys.
66) Convert a map’s keys to a list using streams
Approach 1 — stream over keySet():
List<K> keys = [Link]().stream().toList();
Approach 2 — stream entries then map to key:
List<K> keys2 =
[Link]().stream().map([Link]::getKey).toList();
Explanation: Symmetric to values conversion.
67) Sort a map by its values using streams
Approach 1 — collect to LinkedHashMap to preserve ordering:
LinkedHashMap<K,V> sorted = [Link]().stream()
.sorted([Link]())
.collect([Link]([Link]::getKey, [Link]::getValue,
(a,b)->a, LinkedHashMap::new));
Approach 2 — collect to sorted List of entries (if map not required):
List<[Link]<K,V>> list = [Link]().stream()
.sorted([Link]([Link]()))
.toList();
Explanation: Use LinkedHashMap to retain sorted iteration order when a map is needed.
68) Find intersection of two lists using streams
Approach 1 — hash lookup (fast):
Set<T> setB = new HashSet<>(b);
List<T> inter = [Link]().filter(setB::contains).distinct().toList();
Approach 2 — filter(b::contains) (simpler but O(n*m)):
List<T> inter2 = [Link]().filter(b::contains).distinct().toList();
Explanation: Use a HashSet of the smaller collection for O(n) behavior.
69) Find union of two lists using streams
Approach 1 — concat + distinct:
List<T> union = [Link]([Link](),
[Link]()).distinct().toList();
Approach 2 — set union preserving order:
Set<T> unionSet = new LinkedHashSet<>(a);
[Link](b);
List<T> union2 = new ArrayList<>(unionSet);
Explanation: distinct dedupes combined stream; LinkedHashSet retains insertion order.
70) Find difference between two lists using streams
Approach 1 — A \ B with hash lookup:
Set<T> setB = new HashSet<>(b);
List<T> diff = [Link]().filter(x -> ).toList();
Approach 2 — removeAll on a copy (non-stream):
List<T> copy = new ArrayList<>(a);
[Link](b); // copy now contains a minus b
Explanation: Hash-based filter is efficient; removeAll is simple and mutates copy.