🚀 Java Streams & Functional Programming — Interview Prep
1. Lambda Expression — Foundation
Traditional:
List<String> names = Arrays.asList("Ramesh", "John", "David");
for (String name : names) {
System.out.println(name);
}Functional:
names.forEach(name -> System.out.println(name));Even simpler:
names.forEach(System.out::println);Interview question
What is a lambda?
A lambda is an anonymous function that allows us to pass behavior as a value.
(a, b) -> a + b2. Functional Interface
A functional interface contains exactly one abstract method.
@FunctionalInterface
interface Calculator {
int calculate(int a, int b);
}Usage:
Calculator addition = (a, b) -> a + b;
System.out.println(addition.calculate(10, 20));Output:
30Important built-in functional interfaces:
| Interface | Method | Example |
|---|---|---|
Predicate<T> | test() | filtering |
Function<T,R> | apply() | transformation |
Consumer<T> | accept() | processing |
Supplier<T> | get() | supplying |
UnaryOperator<T> | apply() | T → T |
BinaryOperator<T> | apply() | T,T → T |
3. Predicate — Filtering
Predicate<Integer> isEven = n -> n % 2 == 0;
System.out.println(isEven.test(10));Output:
trueStream example:
List<Integer> numbers = List.of(10, 15, 20, 25, 30);
List<Integer> result = numbers.stream()
.filter(n -> n % 2 == 0)
.toList();
System.out.println(result);Output:
[10, 20, 30]Interview phrase
filter()uses a Predicate because it evaluates a condition and returns true or false.
4. map() — Most Important
Suppose:
List<String> names =
List.of("ramesh", "john", "david");Convert to uppercase:
List<String> result = names.stream()
.map(String::toUpperCase)
.toList();Result:
[RAMESH, JOHN, DAVID]Think:
Input
↓
map()
↓
Transformation
↓
OutputAnother example
List<Integer> numbers = List.of(1, 2, 3, 4);
List<Integer> squares = numbers.stream()
.map(n -> n * n)
.toList();Result:
[1, 4, 9, 16]5. filter() + map()
Very common interview question.
Find squares of even numbers.
List<Integer> result = List.of(1, 2, 3, 4, 5, 6)
.stream()
.filter(n -> n % 2 == 0)
.map(n -> n * n)
.toList();Result:
[4, 16, 36]Pipeline:
1 2 3 4 5 6
↓
filter
↓
2 4 6
↓
map
↓
4 16 366. reduce() — Very Important
Find sum:
int sum = List.of(10, 20, 30, 40)
.stream()
.reduce(0, Integer::sum);Result:
100Conceptually:
0 + 10 + 20 + 30 + 40Another example:
int max = List.of(10, 50, 20, 80, 30)
.stream()
.reduce(Integer.MIN_VALUE, Integer::max);Result:
80Interview question
Difference between map() and reduce()?
map() transforms each element.
reduce() combines multiple elements into a single result.
7. sorted()
List<Integer> result = List.of(50, 10, 30, 20)
.stream()
.sorted()
.toList();Output:
[10, 20, 30, 50]Descending:
List<Integer> result = numbers.stream()
.sorted(Comparator.reverseOrder())
.toList();8. distinct()
List<Integer> numbers =
List.of(10, 20, 10, 30, 20, 40);
List<Integer> result = numbers.stream()
.distinct()
.toList();Output:
[10, 20, 30, 40]9. limit() and skip()
List<Integer> result = numbers.stream()
.skip(2)
.limit(3)
.toList();Very useful for pagination-like processing, although for database-backed pagination you should generally paginate at the database/query level rather than loading everything into memory first.
10. anyMatch / allMatch / noneMatch
boolean result = numbers.stream()
.anyMatch(n -> n > 100);Other examples:
numbers.stream()
.allMatch(n -> n > 0);numbers.stream()
.noneMatch(n -> n < 0);Interview tip
These are short-circuiting terminal operations.
The stream may stop processing as soon as the answer is known.
11. findFirst() / findAny()
Optional<Integer> result = numbers.stream()
.filter(n -> n > 50)
.findFirst();Always remember:
Optional<T>rather than assuming a value exists.
12. flatMap() ⭐⭐⭐
This is a very common senior-level interview question.
Suppose:
List<List<Integer>> numbers = List.of(
List.of(1, 2, 3),
List.of(4, 5),
List.of(6, 7)
);We want:
1 2 3 4 5 6 7Use:
List<Integer> result = numbers.stream()
.flatMap(List::stream)
.toList();map() vs flatMap()
map():
List<List<Integer>>
↓
List<Stream<Integer>>flatMap():
List<List<Integer>>
↓
List<Integer>Real-world example
List<Employee> employees;Each employee has:
List<String> skills;Get all unique skills:
List<String> skills = employees.stream()
.flatMap(e -> e.getSkills().stream())
.distinct()
.sorted()
.toList();This is a great interview example.
13. Collectors.groupingBy() ⭐⭐⭐
Suppose:
class Employee {
String name;
String department;
double salary;
}Group employees by department:
Map<String, List<Employee>> employeesByDept =
employees.stream()
.collect(Collectors.groupingBy(
Employee::getDepartment
));Result conceptually:
IT → [Ramesh, John]
Finance → [David, Peter]
HR → [Sita]Count employees by department
Map<String, Long> countByDept =
employees.stream()
.collect(Collectors.groupingBy(
Employee::getDepartment,
Collectors.counting()
));14. Grouping + Summing
Total salary by department:
Map<String, Double> salaryByDept =
employees.stream()
.collect(Collectors.groupingBy(
Employee::getDepartment,
Collectors.summingDouble(
Employee::getSalary
)
));This is a very good Java 8 interview problem.
15. PartitioningBy()
Unlike grouping, partitioning creates two groups based on true/false.
Example:
Map<Boolean, List<Integer>> result =
numbers.stream()
.collect(Collectors.partitioningBy(
n -> n % 2 == 0
));Conceptually:
true → even numbers
false → odd numbers16. Find Highest Salary
Optional<Employee> employee =
employees.stream()
.max(Comparator.comparing(
Employee::getSalary
));Safely:
employee.ifPresent(e ->
System.out.println(e.getName()));17. Second Highest Salary ⭐⭐⭐
Classic interview question.
Optional<Double> secondHighest =
employees.stream()
.map(Employee::getSalary)
.distinct()
.sorted(Comparator.reverseOrder())
.skip(1)
.findFirst();Pipeline:
Employees
↓
Salary
↓
Distinct
↓
Descending sort
↓
Skip highest
↓
Second highest18. Convert List → Map
Map<Long, Employee> employeeMap =
employees.stream()
.collect(Collectors.toMap(
Employee::getId,
Function.identity()
));Duplicate keys
Important interview trap.
This can fail:
Collectors.toMap(
Employee::getDepartment,
Function.identity()
)if multiple employees have the same department.
Handle it:
Collectors.toMap(
Employee::getDepartment,
Function.identity(),
(e1, e2) -> e1
)19. String Frequency — Classic Coding Question
String input = "banana";
Map<Character, Long> frequency =
input.chars()
.mapToObj(c -> (char) c)
.collect(Collectors.groupingBy(
Function.identity(),
Collectors.counting()
));Result:
b → 1
a → 3
n → 220. Remove Duplicate Characters
String result = "programming"
.chars()
.mapToObj(c -> String.valueOf((char) c))
.distinct()
.collect(Collectors.joining());21. Functional Composition ⭐⭐⭐
Suppose:
Function<Integer, Integer> multiplyBy2 =
n -> n * 2;
Function<Integer, Integer> add10 =
n -> n + 10;Compose:
Function<Integer, Integer> result =
multiplyBy2.andThen(add10);System.out.println(result.apply(5));Output:
20Because:
5 × 2 = 10
10 + 10 = 2022. Method References
Instead of:
names.forEach(name -> System.out.println(name));Use:
names.forEach(System.out::println);Types:
String::toUpperCase
Employee::getName
System.out::println
Integer::sum23. Stream Pipeline — Interview Concept ⭐⭐⭐⭐⭐
Remember:
SOURCE
↓
INTERMEDIATE OPERATIONS
↓
TERMINAL OPERATIONExample:
employees.stream() // Source
.filter(e -> e.getSalary() > 100000) // Intermediate
.map(Employee::getName) // Intermediate
.sorted() // Intermediate
.toList(); // TerminalImportant
Intermediate operations are generally lazy.
Nothing actually happens until a terminal operation triggers evaluation.
24. Streams Are Not Collections
Excellent interview question:
Is Stream a data structure?
No.
A Collection stores data.
A Stream represents a pipeline for processing data.
Collection
↓
Stream
↓
Processing
↓
ResultA stream normally doesn't modify the original collection.
25. Parallel Stream — Architect-Level Question ⭐⭐⭐⭐⭐
numbers.parallelStream()
.map(...)
.toList();Don't say:
"Parallel stream is always faster."
That's wrong.
Parallel streams use the ForkJoinPool/common pool by default and introduce overhead.
Good candidates for parallelism:
CPU-intensive operations
Large datasets
Independent operations
Poor candidates:
Small collections
Blocking I/O
DB calls
Network calls
Operations with shared mutable state
Dangerous
List<Integer> result = new ArrayList<>();
numbers.parallelStream()
.forEach(result::add);This introduces unsafe shared mutation.
Prefer:
List<Integer> result =
numbers.parallelStream()
.map(...)
.toList();🧠10 Questions You Should Practice Before Monday
map vs flatMap?
map vs filter?
Intermediate vs terminal operations?
Why are Streams lazy?
Can a Stream be reused?
Stream vs Collection?
Sequential vs parallel Stream?
How does
groupingBy()work?How do you handle duplicate keys in
toMap()?Why should we avoid side effects in Streams?
⭐ Senior Architect answer
If asked "What is the biggest advantage of functional programming?", don't just say "less code."
Say:
Functional programming encourages declarative, composable and side-effect-minimized code. This makes transformations easier to reason about, test and compose, and can make parallel processing safer when operations are stateless and independent.
That sounds much more Senior Architect level than simply explaining filter() and map().
No comments:
Post a Comment