← Home
☕Java Developer Interview Questions — Fresher to 5 YOEJava Dev InterviewNEW
🌊

Java 8 — Functional Programming

Streams, lambdas, Optional, default methods · 25 questions

1

What is a lambda expression in Java, and what is its syntax?

Must Know🌱 Fresher

A lambda expression is a compact, anonymous way to write an implementation of a functional interface (an interface with exactly one abstract method) — essentially an inline function value. Syntax: (parameters) -> expression or (parameters) -> { statements; }.

Lambda vs anonymous class (same thing, less noise)java
// Before Java 8
Comparator<String> byLength = new Comparator<String>() {
    public int compare(String a, String b) { return a.length() - b.length(); }
};

// Java 8 lambda -- same functional interface, far less boilerplate
Comparator<String> byLength = (a, b) -> a.length() - b.length();
Java 8Lambda
2

What is a functional interface, and what does @FunctionalInterface do?

Must Know🌱 Fresher

A functional interface has exactly one abstract method (it can still have any number of default and static methods, which don't count) — that single abstract method is what a lambda expression provides an implementation for. @FunctionalInterface is optional but recommended: it's a compile-time check that fails the build if you accidentally add a second abstract method, breaking the interface's lambda-compatibility.

A functional interfacejava
@FunctionalInterface
interface Calculator {
    int calculate(int a, int b); // the single abstract method
    default Calculator andLog() { return (a, b) -> { int r = calculate(a, b); System.out.println(r); return r; }; }
}
Java 8Functional Interface
3

What are the core built-in functional interfaces (Function, Predicate, Consumer, Supplier)?

Must Know🌱 Fresher
InterfaceAbstract methodPurpose
Function<T,R>R apply(T t)Transform a T into an R
Predicate<T>boolean test(T t)A true/false condition on T
Consumer<T>void accept(T t)Do something with T, return nothing
Supplier<T>T get()Produce a T with no input
BiFunction<T,U,R>R apply(T t, U u)Transform two inputs into an R
UnaryOperator<T>T apply(T t)Function where input/output are the same type
Java 8Functional Interface
4

What is the Stream API, and what is the difference between intermediate and terminal operations?

Must Know🌱 Fresher

A Stream is a sequence of elements supporting a pipeline of functional-style operations — it doesn't store data itself, it processes data from a source (a collection, array, or generator) on demand.

🌊 Stream pipeline
source          intermediate ops (lazy)             terminal op (triggers execution)
list.stream()  -> .filter(...)  -> .map(...)   ->   .collect(toList())
                   |                |                     |
              returns a new       returns a new      consumes the stream,
              Stream (no work     Stream (no work    produces a result,
              done yet)           done yet)          stream can't be reused

Intermediate operations (filter, map, sorted...) return a new stream and are lazy. Terminal operations (collect, forEach, count, reduce...) actually trigger processing and produce a result or side effect — after a terminal op runs, the stream is consumed and can't be reused.

Java 8Streams
5

What is the difference between map() and flatMap()?

Must Know🌱 Fresher

map() transforms each element into exactly one new element (1-to-1) — Stream<T> → Stream<R>. flatMap() transforms each element into a stream of elements, then flattens all those inner streams into one single stream — used when each input maps to zero, one, or many outputs (commonly, "flatten a list of lists").

map produces nested streams; flatMap flattens themjava
List<List<Integer>> nested = List.of(List.of(1, 2), List.of(3, 4));

nested.stream().map(List::stream);                 // Stream<Stream<Integer>> -- nested, awkward
nested.stream().flatMap(List::stream).toList();     // Stream<Integer> -- flattened: [1, 2, 3, 4]
Java 8Streams
6

What are Collectors.toList(), groupingBy(), joining(), and partitioningBy() used for?

Must Know📊 Mid
Common Collectorsjava
List<String> names = people.stream().map(Person::getName).collect(Collectors.toList());

Map<String, List<Person>> byCity = people.stream()
    .collect(Collectors.groupingBy(Person::getCity));

String csv = names.stream().collect(Collectors.joining(", "));

Map<Boolean, List<Person>> adultsVsMinors = people.stream()
    .collect(Collectors.partitioningBy(p -> p.getAge() >= 18));
Java 8Collectors
7

What is Optional, and what problem does it solve?

Must Know🌱 Fresher

Optional<T> is a container that either holds a non-null value or is empty — it exists to make "this might not have a value" explicit in the type system, as an alternative to returning null and hoping every caller remembers to check for it.

Common Optional usagejava
Optional<User> maybeUser = repo.findById(id);
maybeUser.ifPresent(u -> System.out.println(u.getName()));
String name = maybeUser.map(User::getName).orElse("unknown");
Java 8Optional
8

What terminal operations does Stream provide besides collect()?

Must Know🌱 Fresher
  • forEach(Consumer) — perform an action per element, no result.
  • count() — number of elements.
  • anyMatch/allMatch/noneMatch(Predicate) — boolean checks, short-circuiting.
  • findFirst()/findAny() — an Optional with the first (or any, useful for parallel streams) matching element.
  • min/max(Comparator) — an Optional with the extreme element.
  • toArray() — collect into an array.
Java 8Streams
9

What are method references, and what types are there?

Important🌱 Fresher

A method reference is shorthand for a lambda that just calls one existing method — four flavors:

Four kinds of method referencejava
Class::staticMethod        // Integer::parseInt          same as: s -> Integer.parseInt(s)
instance::instanceMethod   // myList::add                same as: x -> myList.add(x)
Class::instanceMethod      // String::toUpperCase         same as: s -> s.toUpperCase()
Class::new                 // ArrayList::new              same as: () -> new ArrayList<>()
Java 8Method Reference
10

Are streams lazy? When do intermediate operations actually execute?

Important📊 Mid

Yes. Calling .filter() or .map() just builds up a description of the pipeline — no element is actually processed until a terminal operation is invoked. Once it is, the stream processes elements one at a time, end-to-end through the whole pipeline (not stage-by-stage over the whole collection), which lets short-circuiting operations like findFirst() or limit() stop early without processing every element.

Nothing runs until the terminal opjava
Stream<String> s = list.stream()
    .filter(x -> { System.out.println("filter " + x); return x.length() > 2; })
    .map(x -> { System.out.println("map " + x); return x.toUpperCase(); });
// nothing printed yet -- no terminal operation called

s.forEach(System.out::println); // NOW filter/map run, interleaved per-element
Java 8Streams
11

How do filter() and reduce() work in the Stream API?

Important🌱 Fresher

filter(Predicate) keeps only elements matching a condition, dropping the rest — an intermediate operation. reduce(...) combines all elements into a single result by repeatedly applying a combining function — a terminal operation.

filter then reducejava
int totalOfEvens = numbers.stream()
    .filter(n -> n % 2 == 0)
    .reduce(0, Integer::sum); // identity=0, accumulator sums each element in
Java 8Streams
12

What is the difference between a Stream and a Collection?

Important🌱 Fresher
CollectionStream
Stores data?Yes — an in-memory data structureNo — a pipeline over a data source, computed on demand
TraversableAny number of timesOnce — consumed by a terminal operation
EvaluationEager (already fully built)Lazy (nothing runs until a terminal op)
External vs internal iterationYou drive iteration (for/while loop)The Stream API drives iteration internally — you describe *what*, not *how*
Java 8Streams
13

What are common Optional anti-patterns to avoid?

Important📊 Mid
  • Calling .get() without checking isPresent() first — throws NoSuchElementException on empty, defeating the whole point; use orElse/orElseGet/orElseThrow instead.
  • Using Optional as a field or method parameter — it's designed as a return type; as a field it adds serialization/boilerplate pain for no benefit over just checking null internally.
  • Wrapping a collection in Optional — an empty List already represents "nothing" perfectly well; return an empty collection, never Optional<List<T>>.
  • Calling .isPresent() then .get() instead of the more idiomatic .map()/.ifPresent()/.orElse() chain.
Java 8Optional
14

What is the java.time API introduced in Java 8, and why did it replace java.util.Date?

Important🌱 Fresher

java.util.Date was mutable (unsafe to share/cache), not thread-safe, had a confusing API (months are 0-indexed, years are offset from 1900), and mixed up date/time/timezone concepts in one class. java.time (Java 8, heavily inspired by the popular third-party Joda-Time library) replaced it with clear, immutable, thread-safe types: LocalDate (date only), LocalTime (time only), LocalDateTime (both, no timezone), ZonedDateTime (with timezone), and Duration/Period for spans of time.

Java 8Date/Time
15

What are the different overloads of Stream.reduce(), and what do identity/accumulator/combiner mean?

Important📊 Mid
reduce() overloadsjava
Optional<T> reduce(BinaryOperator<T> accumulator);                 // no identity -- empty stream -> Optional.empty()
T reduce(T identity, BinaryOperator<T> accumulator);                // has identity -- always returns a T
<U> U reduce(U identity, BiFunction<U,T,U> accumulator, BinaryOperator<U> combiner); // parallel-safe
  • identity — the starting value (also the result for an empty stream), and must be a true identity for the combiner (e.g. 0 for sum, 1 for product).
  • accumulator — combines the running result with the next element.
  • combiner — only used in parallel streams, to merge partial results computed by different threads on different chunks — must be present whenever the input and result types differ.
Java 8Streams
16

What fluent Comparator methods did Java 8 add (comparing, thenComparing, reversed)?

Important🌱 Fresher
Fluent Comparator building (Java 8)java
Comparator<Person> byAge = Comparator.comparing(Person::getAge);
Comparator<Person> byAgeThenName = Comparator.comparing(Person::getAge)
                                              .thenComparing(Person::getName);
Comparator<Person> oldestFirst = byAge.reversed();
Comparator<Person> nullsSafe = Comparator.nullsFirst(byAge);
Java 8Comparator
17

What is the difference between Function.andThen() and Function.compose()?

Important📊 Mid

Both chain two functions, but in opposite order of application:

andThen vs composejava
Function<Integer, Integer> times2 = x -> x * 2;
Function<Integer, Integer> plus3  = x -> x + 3;

times2.andThen(plus3).apply(5);  // times2 first, then plus3:  (5*2)+3  = 13
times2.compose(plus3).apply(5);  // plus3 first, then times2:  (5+3)*2 = 16

f.andThen(g) = "do f, then g" (g runs on f's output). f.compose(g) = "do g, then f" (f runs on g's output) — the opposite order, easy to mix up in an interview if you don't have a concrete example memorized.

Java 8Function
18

Why do IntStream, LongStream, and DoubleStream exist instead of just using Stream<Integer>?

Important📊 Mid

A Stream<Integer> stores boxed Integer objects — every element is a separate heap allocation, and every arithmetic operation involves unboxing/reboxing overhead. IntStream/LongStream/DoubleStream work directly with primitive int/long/double values, avoiding that boxing entirely, and add primitive-specific terminal operations like sum(), average(), and summaryStatistics() that wouldn't make sense on a generic Stream<T>. Convert between them with mapToInt/mapToObj etc. when needed.

Java 8StreamsBoxing
19

What are parallel streams, and when do they help or hurt?

Advanced📊 Mid

.parallelStream() (or .parallel()) splits the source and processes chunks concurrently across the common ForkJoinPool, then merges results. It helps for large datasets with genuinely CPU-bound, independent per-element work. It hurts (or just doesn't help) when: the dataset is small (splitting/merging overhead dominates), the source doesn't split efficiently (e.g. LinkedList), the operation involves I/O or blocking (starves the shared pool other parts of the app rely on), or the elements aren't independent (shared mutable state across "parallel" iterations reintroduces race conditions).

Java 8StreamsParallelism
20

What are short-circuiting stream operations?

Advanced📊 Mid

A short-circuiting operation can produce a result (or terminate the pipeline) without processing every element of the source — critical for handling infinite or very large streams. Examples: limit(n) (intermediate — stops pulling once n elements are collected), findFirst()/findAny(), anyMatch/allMatch/noneMatch (terminal — stop as soon as the answer is determined, e.g. anyMatch returns true on the first match without checking the rest).

Java 8Streams
21

Why is it awkward to throw checked exceptions from a lambda, and how do you work around it?

Advanced📊 Mid

None of the standard functional interfaces (Function, Consumer, etc.) declare any checked exceptions in their abstract method's signature — so a lambda implementing them cannot throw a checked exception directly; it won't compile. Common workarounds: catch and wrap the checked exception as an unchecked one inside the lambda, or define your own functional interface whose method does declare throws Exception and use that instead of the standard Function.

Wrapping a checked exception inside a lambdajava
list.stream().map(path -> {
    try {
        return Files.readString(Path.of(path)); // readString throws checked IOException
    } catch (IOException e) {
        throw new UncheckedIOException(e); // rethrow as unchecked to satisfy Function<String,String>
    }
});
Java 8LambdaExceptions
22

How would you write your own functional interface?

Optional🌱 Fresher
Writing your own functional interfacejava
@FunctionalInterface
interface TriFunction<A, B, C, R> {
    R apply(A a, B b, C c); // exactly one abstract method
}

TriFunction<Integer, Integer, Integer, Integer> sumThree = (a, b, c) -> a + b + c;
Java 8Functional Interface
23

How do Stream.iterate() and Stream.generate() create infinite streams safely?

Optional📊 Mid

Stream.iterate(seed, fn) produces an infinite stream by repeatedly applying fn to the previous value; Stream.generate(supplier) produces an infinite stream by calling a Supplier with no relation between elements. Both are only safe combined with a short-circuiting operation like limit() — otherwise a terminal operation like forEach or collect would try to consume the stream forever.

Infinite streams, always bounded with limit()java
Stream.iterate(1, n -> n * 2).limit(10).forEach(System.out::println); // 1,2,4,8,...,512
Stream.generate(Math::random).limit(5).forEach(System.out::println);
Java 8Streams
24

What does Stream.peek() do, and why is it risky to rely on for logic?

Optional📊 Mid

peek(Consumer) is an intermediate operation meant purely for side-effect observation (e.g. logging each element as it passes through) — it passes elements through unchanged. It's risky to rely on for real logic because, being lazy like every intermediate operation, the JVM is free to skip calling it entirely if the overall pipeline determines it doesn't need to process an element (e.g. short-circuiting on a match before reaching later elements) — its execution isn't guaranteed per-element the way a plain for loop body is.

Java 8Streams
25

When should you NOT use streams, and just use a plain loop instead?

Optional📊 Mid

When a plain loop is more readable: logic with early break/ multiple exit conditions, code that needs to mutate external state heavily (streams are meant to be side-effect-free), simple single-pass operations where a for-loop is just as short and clearer to a wider range of readers, or performance-critical hot paths where the lambda/boxing overhead of streams measurably matters (streams have some inherent overhead versus a hand-written loop, usually negligible but occasionally significant in tight loops). Streams shine for multi-step transform/filter/collect pipelines — forcing a simple loop into stream form "because it's Java 8" often makes code harder to read, not easier.

Java 8Streams