Java Lambda Expressions & Functional Interfaces Interview Questions and Answers (2026) Interview Questions | JiQuest

add

#

Java Lambda Expressions & Functional Interfaces Interview Questions and Answers (2026)

BACKEND INTERVIEW PREPARATION
Java Lambda Expressions & Functional Interfaces
Master 100+ lambda and functional interface interview questions covering method references, built-in functional interfaces, closures, and functional programming patterns for 2026 Java developer roles.
⏳ 45 min read 📝 100+ Q&As 🎯 Beginner to Advanced
⚡ Quick Reference
Functional InterfaceInterface with exactly ONE abstract method (SAM)
Lambda Syntax(params) -> expression OR (params) -> { statements; }
Method ReferenceClass::method — shorthand for lambdas that call one method
Function<T,R>Takes T, returns R. apply(T t)
Consumer<T>Takes T, returns void. accept(T t)
Supplier<T>No input, returns T. get()
Predicate<T>Takes T, returns boolean. test(T t)
Effectively FinalVariable not reassigned after initial assignment — can be used in lambda
Built-in Functional Interface Hierarchy
Function<T,R>
T → R
UnaryOperator<T>
T → T
BiFunction<T,U,R>
T,U → R
BinaryOperator<T>
T,T → T
Predicate<T>
T → boolean
BiPredicate<T,U>
T,U → boolean
Consumer<T>
T → void
Supplier<T>
() → T

Lambda & Functional Interface Interview Questions & Answers

Q1. What is a lambda expression in Java?

A: A lambda expression is an anonymous function — a concise way to represent an instance of a functional interface. Syntax: (parameters) -> body. Introduced in Java 8 to enable functional programming patterns and simplify code like event handlers, Comparators, and Stream operations.

// Anonymous class
Comparator<String> c1 = new Comparator<String>() {
    public int compare(String a, String b) { return a.compareTo(b); }
};
// Lambda equivalent
Comparator<String> c2 = (a, b) -> a.compareTo(b);

Q2. What is a functional interface?

A: An interface with exactly one abstract method (SAM — Single Abstract Method). Can have multiple default/static/private methods. The @FunctionalInterface annotation enforces this at compile time. Examples: Runnable, Comparator, Callable, Function, Predicate, Consumer, Supplier.

Q3. What does the @FunctionalInterface annotation do?

A: It's a documentation/enforcement annotation. If the annotated interface has 0 or more than 1 abstract methods, the compiler throws an error. Without it, any SAM interface is functionally equivalent but there's no compile-time safety. Best practice: always add @FunctionalInterface to intentional functional interfaces.

Q4. What are the different lambda syntax forms?

A: Lambda syntax variations:

// No parameters
Runnable r = () -> System.out.println("run");

// Single parameter (parentheses optional)
Consumer<String> c = s -> System.out.println(s);

// Multiple parameters
Comparator<Integer> comp = (a, b) -> a - b;

// Block body (explicit return)
Function<Integer, Integer> square = n -> {
    int result = n * n;
    return result;
};

// With type declarations (optional, inferred)
Function<String, Integer> len = (String s) -> s.length();

Q5. What are the 4 types of method references?

A:

TypeSyntaxLambda Equivalent
Static methodClass::staticMethodx -> Class.staticMethod(x)
Instance (specific)instance::methodx -> instance.method(x)
Instance (arbitrary)Class::instanceMethod(obj, x) -> obj.method(x)
ConstructorClass::newx -> new Class(x)
Function<String,Integer> parseInt = Integer::parseInt;    // static
Consumer<String> printer = System.out::println;          // instance (specific)
Function<String,Integer> len = String::length;           // instance (arbitrary)
Supplier<List<String>> listFactory = ArrayList::new;     // constructor

Q6. What is the Function interface and what are its key methods?

A: Function<T,R> takes T and returns R. Key methods: apply(T t) — main method. compose(Function before) — apply before first, then this. andThen(Function after) — apply this first, then after.

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

Function<Integer,Integer> times2ThenPlus3 = times2.andThen(plus3);
System.out.println(times2ThenPlus3.apply(5)); // (5*2)+3 = 13

Function<Integer,Integer> plus3ThenTimes2 = times2.compose(plus3);
System.out.println(plus3ThenTimes2.apply(5)); // (5+3)*2 = 16

Q7. What is the Predicate interface and how do you combine predicates?

A: Predicate<T> tests a condition: boolean test(T t). Combinators: and(Predicate p) — logical AND; or(Predicate p) — logical OR; negate() — logical NOT; Predicate.not(p) — static negation (Java 11).

Predicate<Integer> isEven = n -> n % 2 == 0;
Predicate<Integer> isPositive = n -> n > 0;
Predicate<Integer> isEvenAndPositive = isEven.and(isPositive);
Predicate<Integer> isOdd = isEven.negate();
// Java 11:
Predicate<String> notBlank = Predicate.not(String::isBlank);

Q8. What is the Consumer interface?

A: Consumer<T> accepts T and returns void. Method: accept(T t). Chaining: andThen(Consumer after). BiConsumer<T,U> accepts two arguments. Used in forEach, peek operations.

Consumer<String> print = System.out::println;
Consumer<String> printUpper = s -> System.out.println(s.toUpperCase());
Consumer<String> printBoth = print.andThen(printUpper);
printBoth.accept("hello"); // "hello" then "HELLO"

Q9. What is the Supplier interface?

A: Supplier<T> provides a value with no input: T get(). Used for lazy evaluation, factory methods, default values. Example: Optional.orElseGet(Supplier) only calls the supplier if the Optional is empty (lazy) vs orElse() which always evaluates the argument.

Supplier<String> greeting = () -> "Hello " + expensiveCall();
// Only computed when get() is called
Optional<String> opt = Optional.empty();
String val = opt.orElseGet(greeting); // lazy — greeting.get() called only if empty

Q10. What are UnaryOperator and BinaryOperator?

A: UnaryOperator<T> extends Function<T,T> — input and output are the same type. BinaryOperator<T> extends BiFunction<T,T,T> — two inputs and output are the same type. Used in List.replaceAll(), Stream.reduce().

UnaryOperator<String> upper = String::toUpperCase;
BinaryOperator<Integer> max = Integer::max;
list.replaceAll(upper); // replaces each element with its uppercase
int sum = stream.reduce(0, Integer::sum); // uses BinaryOperator

Q11. What are primitive functional interfaces and why do they exist?

A: To avoid boxing/unboxing overhead. Variants: IntFunction<R>, LongFunction<R>, DoubleFunction<R>; ToIntFunction<T>, ToLongFunction, ToDoubleFunction; IntConsumer, LongConsumer, DoubleConsumer; IntSupplier, IntPredicate, IntUnaryOperator, IntBinaryOperator; and BiFunction variants. Use these when working with primitives in performance-critical code.

Q12. What is "effectively final" in the context of lambdas?

A: A local variable used inside a lambda must be either explicitly final or effectively final — meaning it is never reassigned after its initial assignment. The compiler enforces this. Instance fields and static fields have no such restriction.

int x = 10;
Runnable r = () -> System.out.println(x); // OK — x is effectively final
x = 20; // Now x is no longer effectively final — lambda line becomes compile error

// Workaround for mutable state:
int[] counter = {0};
Runnable inc = () -> counter[0]++; // array ref is effectively final, content can change

Q13. What is the difference between lambda and anonymous class?

A:

FeatureLambdaAnonymous Class
this keywordRefers to enclosing classRefers to anonymous class itself
.class fileNo separate .class generatedSeparate $1.class file
StateNo instance fieldsCan have instance fields
Use caseFunctional interfaces onlyAny interface/abstract class

Q14. How does the this keyword work inside a lambda?

A: Inside a lambda, this refers to the enclosing class instance (same as outside the lambda). In an anonymous class, this refers to the anonymous class itself. This is a key semantic difference.

class MyService {
    String name = "Service";
    void demo() {
        Runnable lambda = () -> System.out.println(this.name); // "Service"
        Runnable anon = new Runnable() {
            String name = "Anonymous";
            public void run() { System.out.println(this.name); } // "Anonymous"
        };
    }
}

Q15. What is Comparator.comparing() and how does it work?

A: Comparator.comparing(keyExtractor) creates a Comparator that sorts by the given key. Chain with thenComparing() for secondary sort. reversed() reverses order. nullsFirst()/nullsLast() handle null values.

List<Employee> employees = ...;
employees.sort(
    Comparator.comparing(Employee::getDept)
              .thenComparing(Employee::getSalary, Comparator.reverseOrder())
              .thenComparing(Employee::getName)
);

Q16. What is function composition and how do compose() and andThen() differ?

A: Both create a new function from two functions. f.andThen(g): apply f first, then g on the result — left-to-right. f.compose(g): apply g first, then f on the result — right-to-left (mathematical composition). Remember: andThen = pipe, compose = math notation.

Q17. What is currying in the context of Java lambdas?

A: Currying transforms a function that takes multiple arguments into a chain of functions each taking one argument. Useful for partial application.

// Curried add function
Function<Integer, Function<Integer, Integer>> curriedAdd = a -> b -> a + b;
Function<Integer, Integer> add5 = curriedAdd.apply(5); // partial application
System.out.println(add5.apply(3)); // 8
System.out.println(add5.apply(10)); // 15

Q18. What is partial application vs currying?

A: Currying: converting f(a,b,c) to f(a)(b)(c) — each step takes exactly one argument. Partial application: fixing some arguments of a function to create a new function with fewer arguments. In Java, both are achieved using lambda closures capturing outer variables.

Q19. Can a functional interface have default methods?

A: Yes. @FunctionalInterface allows any number of default and static methods, but only ONE abstract method. Example: Comparator has many default methods (thenComparing, reversed, nullsFirst) but only one abstract method (compare).

Q20. What is BiFunction and when do you use it?

A: BiFunction<T,U,R> takes two inputs (T and U) and returns R. Method: apply(T t, U u). andThen(Function after) transforms the result. Used in Map.compute(), replaceAll(), merge().

BiFunction<String, Integer, String> repeat = (s, n) -> s.repeat(n);
System.out.println(repeat.apply("ab", 3)); // "ababab"
map.replaceAll((key, val) -> val.toUpperCase()); // BiFunction usage

Q21. How do you handle checked exceptions in lambdas?

A: Lambdas cannot throw checked exceptions unless the functional interface declares them (like Callable). Common workarounds: catch inside the lambda, create a wrapper functional interface, or use a utility like Lombok's @SneakyThrows.

// Option 1: catch inside lambda
Consumer<String> readFile = path -> {
    try { Files.readString(Path.of(path)); }
    catch (IOException e) { throw new RuntimeException(e); }
};
// Option 2: checked-exception-aware wrapper
@FunctionalInterface interface ThrowingConsumer<T> {
    void accept(T t) throws Exception;
}

Q22. What is the Runnable functional interface?

A: Runnable has one abstract method: void run(). No input, no output. Used for thread tasks, scheduled jobs, and any void-no-arg operation. Equivalent to Supplier<Void> returning null (but semantically different).

Runnable task = () -> System.out.println("Running in thread");
new Thread(task).start();
executor.submit(task);

Q23. What is the Callable functional interface?

A: Callable<V> has one method: V call() throws Exception. Returns a value and can throw checked exceptions, unlike Runnable. Used with ExecutorService.submit(), returning a Future<V>.

Q24. What is a closure in the context of Java lambdas?

A: A closure is a function that captures variables from its enclosing scope. In Java, lambdas capture effectively-final local variables by value (a copy). They can also capture instance fields and static fields (no restriction). Java closures are not true closures in the functional sense because they cannot mutate captured locals.

Q25. What happens when a lambda captures a local variable?

A: The lambda captures the value of the variable at the time of capture. Since the variable must be effectively final, its value cannot change after capture, so all reads see the original value. The JVM stores the captured value in the lambda's internal representation.

Q26. What are IntConsumer, IntSupplier, IntPredicate, IntFunction?

A: Primitive specializations to avoid autoboxing. IntConsumer: void accept(int value). IntSupplier: int getAsInt(). IntPredicate: boolean test(int value). IntFunction<R>: R apply(int value). ToIntFunction<T>: int applyAsInt(T value). Use these in Stream.mapToInt() chains.

Q27. What is the difference between Function<T,T> and UnaryOperator<T>?

A: Semantically identical — both take T and return T. UnaryOperator<T> extends Function<T,T> and signals intent that the output type equals the input type. API preference: use UnaryOperator when the same type in/out is the design intent (e.g., List.replaceAll(UnaryOperator)).

Q28. How are lambdas implemented internally in the JVM?

A: Lambdas are NOT compiled to anonymous inner classes. Instead, the compiler generates an invokedynamic bytecode instruction. At runtime, LambdaMetafactory creates an implementation of the functional interface using method handles. This is faster (no class loading per lambda), more memory-efficient, and allows future JVM optimizations.

Q29. What is the relationship between lambdas and the Stream API?

A: Streams are the primary consumer of lambdas. Stream methods like filter(), map(), forEach(), reduce(), sorted() all accept functional interfaces (Predicate, Function, Consumer, BinaryOperator, Comparator). Lambdas make stream pipelines concise and readable.

List<String> result = names.stream()
    .filter(s -> s.startsWith("J"))       // Predicate
    .map(String::toUpperCase)              // Function (method ref)
    .sorted(Comparator.naturalOrder())     // Comparator
    .collect(Collectors.toList());         // Collector

Q30. What is a method reference to an instance method of an arbitrary object?

A: Syntax: ClassName::instanceMethod. The first argument becomes the target object. Example: String::toLowerCase is equivalent to (String s) -> s.toLowerCase(). The method is called on each element passed as the first argument.

Function<String, String> lower = String::toLowerCase; // instance method ref
// equivalent to: s -> s.toLowerCase()

BiPredicate<String, String> startsWith = String::startsWith;
// equivalent to: (s, prefix) -> s.startsWith(prefix)

Q31. Can you use var with lambdas in Java 11+?

A: Yes, Java 11 allows var in lambda parameter lists, which is useful when adding annotations to parameters (annotations cannot be added to inferred types). All parameters must either all use var or all omit it — mixing is not allowed.

// Java 11 — var with annotation:
Consumer<String> c = (@NonNull var s) -> System.out.println(s);
// All params must use var or none:
BiFunction<String, Integer, String> f = (var s, var n) -> s.repeat(n); // OK

Q32. What is Predicate.not() introduced in Java 11?

A: Predicate.not(predicate) is a static method that returns the negation of a predicate. More readable than .negate() for method references.

// Before Java 11:
list.stream().filter(s -> !s.isBlank()).collect(toList());
// Java 11:
list.stream().filter(Predicate.not(String::isBlank)).collect(toList());

Q33. What is Comparator.reversed() and naturalOrder()/reverseOrder()?

A: reversed() returns a comparator with reversed order. naturalOrder() uses Comparable's natural ordering. reverseOrder() is the reverse of natural ordering. These are Comparator static/default methods for concise sorting.

list.sort(Comparator.comparing(Employee::getSalary).reversed()); // highest first
list.sort(Comparator.<String>naturalOrder().reversed()); // Z to A

Q34. How do you implement memoization with Function?

A: Memoization caches previous results. Use Map.computeIfAbsent() to wrap a Function with caching.

Map<Integer, Long> cache = new HashMap<>();
Function<Integer, Long> fib = null; // must be effectively final trick
// Use array for self-reference:
Function<Integer, Long>[] fibRef = new Function[1];
fibRef[0] = n -> n <= 1 ? n : cache.computeIfAbsent(n,
    k -> fibRef[0].apply(k-1) + fibRef[0].apply(k-2));

Q35. What are the common anti-patterns with lambdas?

A: 1) Over-using lambdas for complex logic (use named methods instead). 2) Capturing mutable state via array workarounds (signals design issue). 3) Throwing checked exceptions without proper wrapping. 4) Long multi-line lambda bodies (extract to named method). 5) Ignoring return values of chained functions. 6) Using lambda when a method reference is clearer.

Q36. What is a method reference to a constructor?

A: ClassName::new creates a functional interface that calls the constructor. Used as factory methods with Supplier, Function, or BiFunction.

Supplier<ArrayList<String>> factory = ArrayList::new;  // no-arg constructor
Function<Integer, ArrayList> sizedFactory = ArrayList::new; // int constructor
// In stream: collect to set via constructor ref
stream.collect(Collectors.toCollection(TreeSet::new));

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

A: Consumer.andThen(after): chains two consumers — both receive the same input, no return value. Function.andThen(after): chains two functions — input goes to first, output of first goes to second function. They compose differently because Consumer doesn't return a result.

Q38. How do you write a lambda that returns nothing and takes no arguments?

A: Use Runnable: () -> expression or () -> { statements; }. The void return is implicit for expression-body lambdas if the expression itself returns void, or if the body block has no return statement.

Runnable r1 = () -> System.out.println("hi"); // expression body, returns void
Runnable r2 = () -> { int x = 5; System.out.println(x); }; // block body

Q39. What is Function.identity()?

A: Returns a function that returns its input unchanged: t -> t. Useful in stream operations where you need a Function but want the input passed through.

// Convert list to map: element as key and value
Map<String, String> m = list.stream()
    .collect(Collectors.toMap(Function.identity(), String::toUpperCase));

Q40. What is Comparator.nullsFirst() and nullsLast()?

A: These wrap an existing Comparator to handle null values. nullsFirst(c) considers null less than any non-null; nullsLast(c) considers null greater. Use when sorting collections that may contain null elements.

Comparator<String> nullSafe = Comparator.nullsFirst(Comparator.naturalOrder());
list.sort(nullSafe); // nulls come first, then alphabetical

Q41. Can you serialize a lambda?

A: Lambdas can be serialized if the functional interface extends Serializable. However, serialization of lambdas is fragile — the serialized form is tied to the specific JVM implementation. Prefer serializing data, not behavior.

Comparator<String> comp = (Comparator<String> & Serializable) (a, b) -> a.compareTo(b);

Q42. What is the difference between Iterable.forEach() and Stream.forEach()?

A: Iterable.forEach(Consumer) is sequential, ordered, no parallelism, operates on the collection directly. Stream.forEach(Consumer) may be parallel, order not guaranteed in parallel streams (use forEachOrdered for ordering). Stream.forEach() is terminal and cannot be called again.

Q43. What is the purpose of the peek() method in streams and how does it use Consumer?

A: peek(Consumer) is an intermediate stream operation for debugging — applies a Consumer to each element without consuming the stream. Since streams are lazy, peek only fires when a terminal operation drives the pipeline.

List<String> result = list.stream()
    .filter(s -> s.length() > 3)
    .peek(s -> System.out.println("filtered: " + s))
    .map(String::toUpperCase)
    .peek(s -> System.out.println("mapped: " + s))
    .collect(Collectors.toList());

Q44. What are the rules for a lambda to implement a functional interface with generic types?

A: The compiler infers the type parameters from context (target typing). The lambda's parameter types and return type must match the inferred functional interface's abstract method signature. If inference fails, provide explicit casts or type witnesses.

Q45. What is Optional.ifPresentOrElse() and how does it use functional interfaces?

A: Optional.ifPresentOrElse(Consumer, Runnable) — Java 9. Runs Consumer if value present, Runnable if empty. Cleaner than if (opt.isPresent()) ... else ...

Optional<String> name = findUser();
name.ifPresentOrElse(
    n -> System.out.println("Found: " + n),
    () -> System.out.println("Not found")
);

Q46. How do you use Function in a Map.computeIfAbsent()?

A: Map.computeIfAbsent(key, Function<K,V>) computes and stores the value only if the key is absent. The Function receives the key. Commonly used for grouping, caching, and building nested data structures.

Map<String, List<String>> grouped = new HashMap<>();
for (String word : words) {
    grouped.computeIfAbsent(word.substring(0,1), k -> new ArrayList<>()).add(word);
}

Q47. What is the difference between map() and flatMap() in terms of function signatures?

A: map(Function<T, R>): T -> R, wraps result in Optional/Stream element. flatMap(Function<T, Stream<R>>): T -> Stream<R>, then flattens. In Optional: map returns Optional<Optional> risk; flatMap prevents double-wrapping.

// map: String -> String
Stream<String> s1 = Stream.of("a,b", "c,d").map(s -> s.toUpperCase());
// flatMap: String -> Stream<String> then flatten
Stream<String> s2 = Stream.of("a,b", "c,d").flatMap(s -> Arrays.stream(s.split(",")));

Q48. Can a functional interface extend another functional interface?

A: Yes, if the combined abstract methods result in exactly one abstract method. Example: UnaryOperator extends Function — Function has apply(), UnaryOperator inherits it and adds no new abstract methods (only refines the types), so it remains a functional interface.

Q49. How does the compiler determine which overloaded method a lambda targets?

A: The compiler uses target typing — infers the functional interface from the expected type at the call site. If ambiguous (multiple overloads accept compatible functional interfaces), it may fail with a compile error, requiring an explicit cast or type witness.

void process(Runnable r) { }
void process(Callable<?> c) { }
// process(() -> "hello"); // ambiguous — both match
process((Callable<?>) () -> "hello"); // explicit cast resolves it

Q50. What is a higher-order function in Java?

A: A function that takes other functions as arguments or returns functions. Java methods accepting Function, Predicate, Consumer, etc. are higher-order. Examples: Stream.filter(Predicate), Optional.map(Function), Comparator.comparing(Function).

Q51. What is the purpose of Comparator.comparing with a key extractor?

A: Comparator.comparing(Function keyExtractor) creates a comparator by extracting a Comparable key from each element and comparing those keys. Reduces boilerplate compared to full comparator implementations. Optional second arg: a custom key Comparator.

Q52. What are the limitations of lambdas in Java?

A: 1) Cannot have state (no instance fields). 2) Cannot be used as constructors. 3) Cannot have explicit type parameters (generic lambdas not directly supported). 4) Cannot use non-effectively-final local variables. 5) Cannot directly throw checked exceptions unless declared in the functional interface. 6) Cannot refer to themselves (no self-reference for recursion without a workaround).

Q53. How do you implement the Observer pattern with lambdas?

A: Replace Observer interface with Consumer<T>. Store List<Consumer<T>> as listeners. Call each consumer on event.

class EventBus<T> {
    private List<Consumer<T>> listeners = new ArrayList<>();
    void subscribe(Consumer<T> listener) { listeners.add(listener); }
    void publish(T event) { listeners.forEach(l -> l.accept(event)); }
}
bus.subscribe(event -> System.out.println("Received: " + event));

Q54. What is the Predicate.isEqual() factory method?

A: Predicate.isEqual(target) returns a predicate that tests if each input equals the target using Objects.equals(). Null-safe alternative to t -> target.equals(t).

Predicate<String> isJava = Predicate.isEqual("Java");
list.stream().filter(isJava).count(); // count elements equal to "Java"

Q55. How does Optional.map() use Function internally?

A: Optional.map(Function<T,R> mapper) applies mapper to the value if present and wraps in Optional<R>. If empty, returns Optional.empty(). The mapper must not return null (use flatMap if mapper returns Optional).

Optional<String> name = Optional.of("john");
Optional<Integer> len = name.map(String::length);   // Optional[4]
Optional<String> upper = name.map(String::toUpperCase); // Optional[JOHN]

Q56. What is a lambda's bytecode representation?

A: The compiler emits an invokedynamic instruction pointing to LambdaMetafactory.metafactory(). This bootstrap method is called once to link the lambda to a generated implementation class (using MethodHandles). Captured variables are stored in the lambda's implicit fields. This approach is more future-proof than anonymous class compilation.

Q57. Can you use lambdas with generic functional interfaces?

A: Yes. The compiler infers type parameters from context. Generic functional interfaces like Comparator<T>, Function<T,R> are fully compatible with lambdas when target typing can determine T and R from the usage context.

Q58. What is the Comparator.thenComparing() method?

A: Returns a lexicographic-order comparator with a secondary comparator applied when the primary returns 0 (equal elements). Enables multi-field sorting without complex comparator logic.

Comparator<Person> byLastName = Comparator.comparing(Person::getLastName);
Comparator<Person> byLastThenFirst = byLastName.thenComparing(Person::getFirstName);
people.sort(byLastThenFirst);

Q59. What are effectively final rules for loop variables?

A: Loop variables (for, while) are reassigned each iteration and are NOT effectively final. Capturing a loop variable in a lambda is a compile error. The classic workaround: assign to a local effectively-final variable inside the loop.

for (int i = 0; i < 10; i++) {
    // Runnable r = () -> System.out.println(i); // COMPILE ERROR: i is not effectively final
    int copy = i; // effectively final
    Runnable r = () -> System.out.println(copy); // OK
    executor.submit(r);
}

Q60. What is the difference between Predicate<T>.and() and && in a single lambda?

A: Both achieve logical AND. Predicate.and(p) composes two separate Predicate objects (reusable, named). Writing p1 && p2 inside a single lambda is also fine. Use composition when predicates are reusable across multiple places. Use inline lambda for one-off conditions.

Q61. How are lambdas used in Spring Framework?

A: Spring 5+ uses lambdas extensively: WebMvcConfigurer default methods, SecurityFilterChain (Java DSL), RouterFunction for functional web endpoints, @Bean methods with lambdas, RestTemplate/WebClient exchange specs. Spring's move to functional style reduces XML and annotation configuration.

Q62. What is the ExecutorService.submit(Callable) vs submit(Runnable) distinction?

A: submit(Callable<T>) returns Future<T> with a result. submit(Runnable) returns Future<?> with null result (or specific value via submit(Runnable, T)). Use Callable when you need the task's return value; Runnable for fire-and-forget tasks.

Q63. What are the performance implications of using lambdas vs anonymous classes?

A: Lambdas using invokedynamic are generally faster after JVM warmup — the bootstrap is done once. Anonymous classes cause class loading overhead for each definition. However, if a lambda captures state, there's a cost to create the capture object. Stateless lambdas can be singleton instances reused by the JVM.

Q64. What is Function.andThen() vs Runnable chaining?

A: Function.andThen() chains functions that return values — useful for data transformation pipelines. Runnable has no andThen() (it's in Consumer). For chaining side effects, use Consumer.andThen(). For chaining pure transformations, use Function.andThen().

Q65. How do you use lambdas with CompletableFuture?

A: CompletableFuture's async methods (thenApply, thenAccept, thenRun, thenCompose, thenCombine) all accept functional interfaces. thenApply(Function), thenAccept(Consumer), thenRun(Runnable), thenCompose(Function<T,CompletableFuture>).

CompletableFuture.supplyAsync(() -> fetchUser(id))     // Supplier
    .thenApply(user -> user.getEmail())                 // Function
    .thenAccept(email -> sendWelcome(email))            // Consumer
    .exceptionally(ex -> { log(ex); return null; });    // Function<Throwable, T>

Q66. Can you use lambdas in switch expressions (Java 14+)?

A: Switch expressions use -> (arrow) syntax but this is NOT lambda syntax — it's switch arrow labels. However, you can use lambdas inside switch arms. Switch expression result can be passed to a Function or stored in a lambda-compatible context.

Q67. What is the difference between a SAM type and a functional interface?

A: All functional interfaces are SAM types. Not all SAM types are @FunctionalInterface — some legacy interfaces (Runnable, ActionListener) are SAM types compatible with lambdas but predate the @FunctionalInterface annotation. The JVM handles any SAM interface, annotation is for documentation and compiler checking.

Q68. How do you create a reusable lambda pipeline?

A: Chain Function objects for transformation pipelines. Store them as fields or variables. Apply with apply().

Function<String, String> sanitize = s -> s.trim().toLowerCase();
Function<String, String> validate = s -> {
    if (s.isEmpty()) throw new IllegalArgumentException();
    return s;
};
Function<String, String> pipeline = sanitize.andThen(validate);
String result = pipeline.apply("  Hello World  "); // "hello world"

Q69. What is the DoubleFunction, LongToIntFunction, and IntToDoubleFunction?

A: These are cross-primitive functional interfaces. LongToIntFunction: long -> int. IntToDoubleFunction: int -> double. DoubleToIntFunction: double -> int. They allow conversion between primitive types in streams without autoboxing. Used in IntStream.mapToDouble(), LongStream.mapToInt(), etc.

Q70. What is the best practice for long lambda bodies?

A: Extract to a named method and use a method reference. Long lambda bodies hurt readability, are untestable in isolation, and cannot be reused. Single-expression lambdas or very short block lambdas are acceptable; anything longer should be a named private method.

// Bad: long lambda
list.stream().filter(e -> {
    // 20 lines of logic...
});
// Good: method reference to a named method
list.stream().filter(this::isEligible);

Q71. How does Java's type inference work with lambdas?

A: The compiler uses target typing to infer the functional interface. From the interface's abstract method signature, it infers the parameter types and expected return type of the lambda. If type inference fails, you can provide explicit types in the lambda parameter list.

Q72. What is a stateless vs stateful lambda?

A: Stateless: lambda doesn't capture any mutable state — safe for parallel streams, reusable, thread-safe (e.g., s -> s.toUpperCase()). Stateful: captures mutable state or has side effects — problematic in parallel streams (e.g., capturing a counter array). Stateful operations in streams (sorted, distinct) require buffering.

Q73. What is Comparator.comparingInt/comparingLong/comparingDouble?

A: Primitive-specialized versions of Comparator.comparing(). Avoid boxing overhead when sorting by a primitive key. Example: Comparator.comparingInt(String::length) sorts strings by length without boxing lengths to Integer.

list.sort(Comparator.comparingInt(String::length)); // int key, no boxing
list.sort(Comparator.comparingDouble(Employee::getSalary)); // double key

Q74. What is the Map.Entry.comparingByKey() and comparingByValue()?

A: Static methods returning Comparators for Map entries. Used to sort map entries in a stream.

map.entrySet().stream()
   .sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
   .forEach(e -> System.out.println(e.getKey() + "=" + e.getValue()));

Q75. What is lazy evaluation and how do lambdas enable it?

A: Lazy evaluation defers computation until the result is actually needed. Lambdas/Supplier enable lazy evaluation by wrapping computation in a function. Stream pipelines are lazy — intermediate operations are not executed until a terminal operation is invoked.

// Eager: always computed
String val = expensiveCompute();
Optional.of("data").orElse(val); // val already computed

// Lazy: only computed if empty
Optional.of("data").orElseGet(() -> expensiveCompute()); // not called here

Q76. What is a functional composition chain and how do you test it?

A: Extract each function step as a named field or method. Test each function independently, then test the composed chain. This is the key advantage of functional composition over monolithic methods — each step is independently testable.

Q77. Can lambdas be assigned to Object type?

A: No. Lambdas must be assigned to a functional interface type. Object is not a functional interface. The compiler needs the target type to generate the lambda's implementation. You must use a cast to a specific functional interface: (Runnable) () -> {}.

Q78. What is the difference between functional programming and object-oriented programming in Java 8+?

A: Java 8+ supports both. OOP: encapsulate state and behavior in objects, favor mutation. FP: treat computation as function evaluation, favor immutability and stateless transformations. Java's Stream API enables FP-style data processing. Best practice: use FP for data transformation, OOP for domain modeling.

Q79. What is the BiConsumer functional interface?

A: BiConsumer<T,U> accepts two arguments and returns void. Method: accept(T t, U u). Used in Map.forEach().

Map<String, Integer> scores = Map.of("Alice", 95, "Bob", 87);
scores.forEach((name, score) -> System.out.println(name + ": " + score));

Q80. How do you implement retry logic with a Supplier?

A: Wrap a fallible operation in a Supplier and retry on exception.

static <T> T retry(Supplier<T> task, int maxRetries) {
    for (int i = 0; i <= maxRetries; i++) {
        try { return task.get(); }
        catch (Exception e) {
            if (i == maxRetries) throw new RuntimeException(e);
        }
    }
    return null;
}
String result = retry(() -> httpClient.get("/api/data"), 3);

Q81. What is a pipeline pattern using Function?

A: Build a processing pipeline by chaining Function.andThen() calls. Each step transforms the data. The full pipeline is a single composed Function, testable end-to-end.

Q82. How does Optional.flatMap() differ from Optional.map() with Function return types?

A: Optional.map(Function<T,R>): maps to R, wraps in Optional. If mapper returns null, Optional.empty(). Optional.flatMap(Function<T, Optional<R>>): mapper already returns Optional — no double-wrapping. Use flatMap when calling methods that themselves return Optional.

Q83. What is a functional interface with multiple default methods?

A: Common examples: Comparator (reversed, thenComparing, nullsFirst...), Function (andThen, compose, identity), Predicate (and, or, negate, not). Default methods provide utility operations built on the single abstract method, making the interface more powerful while remaining a functional interface.

Q84. Can a lambda be stored in a collection?

A: Yes. A lambda is an object implementing the target functional interface, so it can be stored in any collection typed to that interface.

List<Predicate<String>> filters = new ArrayList<>();
filters.add(s -> !s.isEmpty());
filters.add(s -> s.length() < 100);
// Apply all:
Predicate<String> combined = filters.stream().reduce(x -> true, Predicate::and);
list.stream().filter(combined).forEach(System.out::println);

Q85. What is the Strategy pattern implemented with lambdas?

A: Strategy pattern traditionally uses an interface with multiple implementations. With lambdas, each strategy is simply a lambda assigned to the strategy functional interface — no separate class needed. This dramatically reduces boilerplate.

@FunctionalInterface interface TaxStrategy { double calculate(double amount); }
TaxStrategy standard = amount -> amount * 0.18;
TaxStrategy reduced  = amount -> amount * 0.05;
TaxStrategy exempt   = amount -> 0.0;
double tax = standard.calculate(1000); // 180.0

Q86. What is the Comparator.comparing() with key comparator overload?

A: Comparator.comparing(keyExtractor, keyComparator) allows custom ordering for the key. Useful for case-insensitive sorting or localized ordering.

// Case-insensitive name sort:
list.sort(Comparator.comparing(Person::getName, String.CASE_INSENSITIVE_ORDER));

Q87. What is the difference between Function<Integer,Integer> and IntUnaryOperator?

A: Semantically equivalent but IntUnaryOperator avoids boxing (int -> int, not Integer -> Integer). IntUnaryOperator.applyAsInt(int operand) works with primitives directly. Use in IntStream.map(IntUnaryOperator) for better performance.

Q88. How do you convert between functional interface types?

A: You can't directly cast between functional interfaces even if their abstract methods have the same signature — they're different types. Convert by wrapping in a new lambda.

Runnable r = () -> System.out.println("hello");
// Supplier<Void> s = r; // COMPILE ERROR — different types
Supplier<Void> s = () -> { r.run(); return null; }; // wrap

Q89. What is a chain of responsibility pattern with lambdas?

A: Model handlers as Function<Request, Optional<Response>>. Chain with andThen() or a custom handler chain. Each handler either processes the request or passes it along.

Q90. What are the new functional interface additions in Java 9-21?

A: Java 11: Predicate.not(). Java 9: no major additions but Optional improved (ifPresentOrElse, stream, or). Java 11: String methods returning streams. Java 16+: no new functional interfaces but records/sealed classes add new patterns. The core functional interfaces in java.util.function remain stable from Java 8.

Q91. Can you use lambdas with Streams in a parallel fashion?

A: Yes. stream().parallel() or parallelStream() runs intermediate operations in parallel. Lambdas used must be stateless and non-interfering (no shared mutable state, no modification of the source). Thread safety is the developer's responsibility.

Q92. What is the difference between a lambda and a closure that captures state?

A: A Java lambda can capture final/effectively-final local variables by value. It cannot capture and mutate local variables (no true mutable closures). This is a deliberate design choice for thread safety in parallel streams. Instance fields (via this capture) can be mutated but require explicit synchronization.

Q93. What are the common Collectors that use functional interfaces?

A: Collectors.toMap(keyMapper, valueMapper) uses Function. groupingBy(classifier) uses Function. partitioningBy(predicate) uses Predicate. joining() uses no function. counting() uses no function. mapping(mapper, downstream) uses Function. All return Collector instances.

Q94. How does Comparator relate to the Functional Interface concept historically?

A: Comparator existed since Java 1.2 as a 2-method interface (compare + equals from Object). Java 8 annotated it with @FunctionalInterface retroactively (the equals method inherits from Object and doesn't count as abstract), and added default methods. This made Comparator lambda-compatible without breaking existing code.

Q95. What is method reference to a static method example with Streams?

A: Integer::parseInt, Math::abs, Objects::nonNull, Collections::sort — all static method references used as Functions or Predicates in stream operations.

List<Integer> numbers = stringList.stream()
    .filter(Objects::nonNull)        // Predicate: s -> s != null
    .map(Integer::parseInt)          // Function: s -> Integer.parseInt(s)
    .map(Math::abs)                  // Function: n -> Math.abs(n)
    .collect(Collectors.toList());

Q96. What is ObjIntConsumer, ObjLongConsumer, ObjDoubleConsumer?

A: Specialized BiConsumers where the second argument is a primitive. ObjIntConsumer<T>: void accept(T t, int value). Used in Streams for indexed operations (no standard API uses them directly — more for custom code).

Q97. What happens if a lambda throws an unchecked exception?

A: Unchecked exceptions propagate normally from lambdas. In sequential streams, they propagate to the calling thread. In parallel streams, they may be wrapped in CompletionException and propagated after all threads finish. Always handle exceptions at stream terminal operations when using parallel streams.

Q98. What is the Function.andThen() chain execution model?

A: andThen() applies functions left-to-right eagerly when apply() is called on the composed function. There is no lazy deferral — all chained functions execute sequentially when the result is demanded. For lazy pipelines with complex branching, use Stream instead.

Q99. What is a ToIntBiFunction and when is it used?

A: ToIntBiFunction<T,U>: applyAsInt(T t, U u) returns int. Avoids boxing when a two-input function needs to return an int primitive. Example: comparing two objects and returning a comparison int (like Comparator internally).

Q100. How do you write a generic higher-order function in Java?

A: Use type parameters in the method signature and accept functional interfaces as parameters.

static <T, R> List<R> transform(List<T> list, Function<T, R> mapper) {
    return list.stream().map(mapper).collect(Collectors.toList());
}
List<Integer> lengths = transform(names, String::length);
List<String> upper = transform(names, String::toUpperCase);

Q101. What is the Runnable vs Thread relationship in modern Java?

A: Thread implements Runnable. Creating a Thread subclass is rarely done. The modern approach: pass Runnable (lambda) to Thread or ExecutorService. In Java 21, virtual threads: Thread.ofVirtual().start(() -> task()) — lambdas are central to the virtual thread API.

Q102. What is the advantage of functional interfaces over abstract classes for callbacks?

A: Functional interfaces enable lambda-based callbacks — no class boilerplate. Abstract classes require subclassing (one per callback implementation). Multiple functional interface callbacks can be combined via method references. Functional interfaces are more composable and testable.

Q103. What is a functional interface with a generic bounded type parameter?

A: Example: Comparator<T extends Comparable<T>> or custom. Use when the functional interface's behavior depends on the type having specific capabilities (Comparable, Serializable). The bound is enforced at the call site.

Q104. How do lambdas interact with the Optional API?

A: Optional's entire API is built around functional interfaces: map(Function), flatMap(Function), filter(Predicate), ifPresent(Consumer), ifPresentOrElse(Consumer, Runnable), orElseGet(Supplier), or(Supplier<Optional>), stream() (Java 9). This makes Optional fully compatible with stream pipelines.

Q105. What is the recommended way to document a functional interface?

A: Use @FunctionalInterface. Document in javadoc: what the function represents, what the abstract method should do, expected contract (null handling, exception behavior, thread safety). Also document default methods' behavior relative to the abstract method. Follow the java.util.function package conventions for naming (apply, accept, get, test).

No comments
Leave a Comment