1. What is the use of Lambda expression in Java 8 ?
  2. What is the difference between Lambda expression and an anonymous inner class?
  3. How would you debug lambda expression in a complex chain of operations ?
  4. What is a Functional Interface? Give examples of built-in functional interfaces (Predicate, Consumer, Supplier, Function).
  5. What is the @FunctionalInterface annotation? What happens if you add more than one abstract method to it?
  6. Can you use lambda with non-functional interfaces? Why or why not?
  7. Can you explain the concept of effectively final variables in the context of lambda expressions ? Why can't we modify local variables inside lambda?
  8. How do lambda expressions improve code readability and conciseness?
  9. What are method references? Types of method references (static, instance, constructor)?
  10. Difference between lambda and method reference?
  11. Can lambda expressions throw checked exceptions? How to handle them?
  12. Explain Predicate, Consumer, Supplier, Function with real-world examples.
  13. How does lambda work internally? (JVM level – invokedynamic)
  14. Can we use lambda with default methods in interfaces?
  15. Why are functional interfaces important for Stream API and parallel processing?
  16. Can we create our own functional interface? Example.
  17. What is the target typing in lambda expressions?
  18. What is BiFunction? Explain with example

What is the use of Lambda expression in Java 8 ?

=> Concise Code due to its simplified syntax  

=> It can be used to implement the Functional Interface, We can use Lambda expressions instead of Anonymous class so that we can reduce boilerplate code  

=> Enhances the APIs like Streams 
Syntax : (parameters) -> expression

Functional Interface implementation 


package com.tutorialspoint;


public class Tester {


   public static void main(String[] args) {

      

// Functional Interface implementation using anonymous class

      Calculator sum = new Calculator() {

         @Override

         public int operate(int a, int b) {

            return a + b;

         }

      };

      int result = sum.operate(2,3);

      System.out.println(result);       


      // Functional Interface implementation using lambda expression

      Calculator sum1 = (a,b) -> a + b;

      result = sum1.operate(2,3);

      System.out.println(result);

   }  


   interface Calculator {

      int operate(int a, int b);

   }

}

  

Stream API using Lambda Expression

List<Integer> numbers = Arrays.asList(1, 2, 3, 4);

numbers.stream()

       .filter(n -> n % 2 == 0)

       .forEach(n -> System.out.println(n));

 

Limitations :
=> Can make debugging harder due to its concise syntax 

=> Overuse may reduce code clarity

 

What is the difference between Lambda expression and an anonymous inner class?

Syntax

Requires full declaration

new Interface() { ... }


Concise coding 


(parameters) -> expression 

Example

// Functional Interface implementation using anonymous class

Calculator sum = new Calculator() {

         @Override

         public int operate(int a, int b) {

            return a + b;

         }


int result = sum.operate(2,3);

      System.out.println(result); 



// Functional Interface implementation using lambda expression

      
Calculator sum1 =
(a,b) -> a + b;


result = sum1.operate(2,3);      System.out.println(result);




Can implement interfaces or extend classes with multiple methods.


Limited to functional interfaces (Single abstract method Interface).


Debugging

Easier to debug as it resembles a regular class with a clear structure.



Harder to debug due to lack of explicit class structure.


Performance

Less efficient compared to Lambda expressions


Slightly more efficient; uses invokedynamic bytecode, avoiding class creation.


It reduces the boilerplate code


When to use : 

Use for functional interfaces, concise code, and Stream API operations.


Use for non-functional interfaces, complex logic, or when multiple methods need implementation.


We can use @Override also 

How would you debug a Lambda Expression in a complex chain of operations?

=> Breakdown the chain into smaller steps 

=> Use method peek to log the values during chain intermediate operations. This is called Intermediate inspection

Example : import java.util.Arrays;

import java.util.List;


public class DebugLambda {

    public static void main(String[] args) {

        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

        List<Integer> intermediate = numbers.stream()

                                           .filter(n -> n % 2 == 0) // Step 1: Filter even numbers

                                           .peek(n -> System.out.println("Filtered: " + n)) // Debug output

                                           .map(n -> n * 2) // Step 2: Double the numbers

                                           .peek(n -> System.out.println("Mapped: " + n)) // Debug output

                                           .toList();

        System.out.println("Result: " + intermediate);

    }

}


Output : 

Filtered: 2

Mapped: 4

Filtered: 4

Mapped: 8

Result: [4, 8]


=> Use method limit to test with smaller data sets 

Example : List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8);

numbers.stream()

       .limit(3) // Debug with first 3 elements

       .filter(n -> n % 2 == 0)

       .map(n -> n * 2)

       .forEach(System.out::println);

 

  • Output: Processes only [1, 2, 3], making it easier to verify logic.

=> Use System.out.println or a logging framework (e.g., SLF4J, Log4j) inside lambda expressions.

import java.util.Arrays;

import java.util.List;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;


public class DebugLambda {

    private static final Logger logger = LoggerFactory.getLogger(DebugLambda.class);


    public static void main(String[] args) {

        List<String> names = Arrays.asList("Alice", "Bob", "", "Charlie");

        names.stream()

             .filter(s -> {

                 logger.debug("Filtering: {}", s);

                 return !s.isEmpty();

             })

             .map(s -> {

                 String result = s.toUpperCase();

                 logger.debug("Mapping {} to {}", s, result);

                 return result;

             })

             .forEach(s -> logger.debug("Final: {}", s));

    }

}


=> Use a Debugger with Breakpoints (Modern IDEs (e.g., IntelliJ IDEA, Eclipse) allow setting breakpoints inside or around lambda expressions to inspect variables and flow)

=> Move complex lambda logic to named methods for clarity and testability.

=> Wrap risky operations in try-catch to catch and log exceptions.

=> Verify each operation in the chain before combining them.

 

What is a Functional Interface? Give examples of built-in functional interfaces (Predicate, Consumer, Supplier, Function).

=> Functional Interface is also known as Single Abstract Method (SAM) Interface 

=> Functional Interface must have Single Abstract Method only but can multiple default and static methods 

=> Methods inherited from Object class (Eg. toString(), equals() ) won’t come under SAM restriction 

=> Lambda expression can be used to implement the Functional Interface

=> Functional Interface is the backbone for the features Lambda Expressions, Method References and Stream API 

=> Functional Interface improves code readability and reduces boiler plate code 

=> Annotation @FunctionalInterface is optional but recommended because it ensures compile-time check for Single Abstract Method rule 

=> Examples for Predefined Functional Interface :  

Predicate<T>: Represents a function that takes an argument and returns a boolean (test(T t)).


Example for Functional Interface : 

@FunctionalInterface

public interface MyFunctionalInterface {

    void performAction(); // Single abstract method


    // Default method (does not count as abstract)

    default void defaultMethod() {

        System.out.println("This is a default method.");

    }


    // Static method (does not count as abstract)

    static void staticMethod() {

        System.out.println("This is a static method.");

    }

}


Implementation of Functional Interface using Lambda Expression : 

MyFunctionalInterface action = () -> System.out.println("Action performed!");

action.performAction(); // Output: Action performed!


Example with Built-in Functional Interface:

import java.util.function.Predicate;


public class Main {

    public static void main(String[] args) {

        Predicate<Integer> isEven = n -> n % 2 == 0;

        System.out.println(isEven.test(4)); // Output: true

        System.out.println(isEven.test(5)); // Output: false

    }

}

 

Examples of built-in functional interfaces : 

Functional InterfaceInput?Output?Used byExample
Consumer<T>Yes (T)No (void)forEach()s -> System.out.println(s)
Supplier<T>NoYes (T)supply()() -> "Hello"
Function<T,R>Yes (T)Yes (R)map()s -> s.toUpperCase()
Predicate<T>Yes (T)Yes (boolean)filter()s -> s.length() > 5
=> Predicate: Test condition (e.g., isAdult = age -> age >= 18)
=> Consumer: Perform action (e.g., print = s -> System.out.println(s))

=>
Supplier: Provide value (e.g., getTime = () -> LocalDateTime.now())
=>
Function: Transform (e.g., toUpper = s -> s.toUpperCase())


What is the @FunctionalInterface annotation? What happens if you add more than one abstract method to it?

=> Marker annotation that ensures the interface has exactly one abstract method. 

=> If more than one: Compiler error.

=> Purpose: Compile-time check for lambda target types.


Can you use lambda with non-functional interfaces? Why or why not?

=> No — compiler error. Lambda requires a functional interface as target type.

 

Can you explain the concept of effectively final variables in the context of lambda expressions ? Why can't we modify local variables inside lambda?

A variable is effectively final in the following two cases

=> Variable is explicitly declared with the keyword final 

=> Variable is not modified after its initialization even though its not declared with the keyword final. Ie That variable is immutable. 


What will happen if we try to modify the local variable that is used in lambda expression ?

=> If we try to modify the local variable used in lambda expression, compiler will throw an error “Variable used in Lambda expression should be final or effectively final” 

 

Example : 

public class LambdaExample {

    public static void main(String[] args) {

        int number = 10; // Not declared final, but effectively final if not modified

        Runnable r = () -> System.out.println("Number: " + number); // Lambda captures 'number'

        r.run();

        

        // number = 20; // If uncommented, this causes a compilation error

    }

}


Why is this effectively final rule applicable only for Local variables but not for Instance/Static variables ?

=> Because, Instance/Static variables are tied to the Object or Class lifecycle, not the local scope. Modifying them does not introduce the same concurrency risks as local variables in lambda expressions 


Example : 

class Test {

    int instanceVar = 100; // Not subject to effectively final rule

    void method() {

        int localVar = 10; // Must be effectively final

        Runnable r = () -> System.out.println(localVar + instanceVar);

        instanceVar = 200; // Allowed

        // localVar = 20; // Compilation error if modified

        r.run();

    }

}


What is the benefit of effectively final rule : 

=> It provides thread safety and code clarity 

        => To avoid concurrency issues (lambda may run later or in parallel).

 

Is there any workaround to modify the local variable that used in Lambda expression ?


=> By using wrapper classes ( For Example, AtomicInteger (for integers) or AtomicReference (for any object). These are thread safe. As there reference not changing, it works  

=> We can use ArrayList, Arrays also, same logic, that reference not changing

 

How do lambda expressions improve code readability and conciseness?

=> Reduce boilerplate (no anonymous class), enable functional style (Streams), make code shorter and expressive.

What are method references? Types of method references (static, instance, constructor)?

=> A shorthand syntax for a lambda expression
=>Types: Static (Class::method), Instance (object::method), Constructor (Class::new). 
Some Examples : 
Integer::parseInt (instead of x -> Integer.parseInt(x))
System.out::println (instead of x -> System.out.println(x))
String::toUpperCase (instead of x -> x.toUpperCase())
ArrayList::new (instead of () -> new ArrayList<>())
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.forEach(System.out::println); // Method reference instead of x -> System.out.println(x)


Difference between lambda and method reference?

=> Lambda is explicit code; method reference is shorter when delegating to existing method.

Can lambda expressions throw checked exceptions? How to handle them?

=> Yes, but must handle inside lambda or declare in functional interface.

Explain Predicate, Consumer, Supplier, Function with real-world examples.

=> Predicate: Test condition (e.g., isAdult = age -> age >= 18)
=> Consumer: Perform action (e.g., print = s -> System.out.println(s))

=>
Supplier: Provide value (e.g., getTime = () -> LocalDateTime.now())
=>
Function: Transform (e.g., toUpper = s -> s.toUpperCase())

How does lambda work internally? (JVM level – invokedynamic)

=> Uses invokedynamic bytecode (Java 7+) — efficient, no extra class creation.

Can we use lambda with default methods in interfaces?

=> Yes, but lambda targets the single abstract method (default methods are ignored for target typing).

=> Why the Answer is "Yes"
You can use a lambda expression with an interface that has default methods, as long as there is exactly one abstract method (the default methods don't count against the "one abstract method" rule).

Why are functional interfaces important for Stream API and parallel processing?

=> Stream methods (filter, map, forEach) use them. Lambdas enable parallel execution (parallelStream()).

Can we create our own functional interface? Example.

=> Yes 

=> Example : 

@FunctionalInterface
interface MyCalc {
    int add(int a, int b);
}
MyCalc calc = (a, b) -> a + b;


What is the target typing in lambda expressions?

=> Compiler infers type from context (e.g., assignment to Predicate<T>). Helps with overloaded methods.

What is Target Typing in Lambda Expressions?
Target typing means the compiler automatically figures out what type the lambda expression should be, based on the context where it's used (e.g., the type of the variable it's assigned to, or the parameter it's passed to).
In Java 8, lambdas don't have their own type — they are inferred from the target functional interface type in the context.
Why It Matters

Java has many overloaded methods (same name, different parameters).
The compiler uses the target type to decide which lambda matches which method/assignment.
This makes lambdas work cleanly without you explicitly casting or specifying the type.

Simple Example (Assignment Context)
JavaPredicate<String> p = s -> s.length() > 5;  // Target type is Predicate<String>
Here:

The left side is Predicate<String>.
Compiler infers: "This lambda must implement Predicate<String>'s test(String) method".
So, s is automatically String, and return type is boolean.

If you wrote:
JavaObject obj = s -> s.length() > 5;  // Error! Ambiguous — compiler doesn't know what type
Example with Overloaded Methods (Best Use Case)
Javaclass MyClass {
    void process(Predicate<String> p) { ... }     // Takes Predicate<String>
    void process(Function<String, Integer> f) { ... }  // Takes Function<String, Integer>
}

MyClass obj = new MyClass();

obj.process(s -> s.length() > 5);  // Compiler picks Predicate version
// because the lambda returns boolean (matches test() in Predicate)
If you had a lambda returning Integer:
Javaobj.process(s -> s.length());  // Compiler picks Function version
Another Real-World Example (Stream)
JavaList<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.stream().filter(s -> s.startsWith("A"));  // Compiler knows filter takes Predicate<String>
Key Points for Interviews

Target typing = context-based inference of lambda's functional interface type.
Helps avoid ambiguity in overloaded methods.
If context is missing (e.g., assigning to Object), you get compile error.

i.e "Target typing is how the compiler infers the functional interface type for a lambda based on the context it's used in, like assignment or method parameter. This resolves overloaded methods automatically." 


What is BiFunction? Explain with example

=> BiFunction<T,U,R> is a functional interface, introduced in Java 8 

=> It takes two input parameters(types T, U) and returns one Result (type R) 

=> Function<T, R>: one input → one output. 
=> BiFunction<T, U, R>: two inputs → one output.

=> Code Example : 

import java.util.function.BiFunction;
public class LambdaExercises {
    public static void main(String[] args) {
        // Use BiFunction to add two numbers with lambda
        BiFunction<Integer, Integer, Integer> addFunction = (a, b) -> a + b;

        // Test it
        int result = addFunction.apply(10, 20);
        System.out.println("Sum of 10 and 20 using BiFunction lambda: " + result);

        // More tests
        System.out.println("Sum of 5 and 7: " + addFunction.apply(5, 7));  // 12
        System.out.println("Sum of 100 and -50: " + addFunction.apply(100, -50));  // 50
    }
}

=> Alternative with Method Reference (Cleaner) :

BiFunction<Integer, Integer, Integer> addFunction = Integer::sum;

Integer::sum is a static method reference for int sum(int a, int b) — same as (a, b) -> a + b.

______________________________________________________________________

Practice the following coding exercises (easy-medium)

  1. Use lambda with forEach to print a list of strings with "Hello " prefix.
  2. Sort a list of integers in descending order using lambda Comparator.
  3. Filter even numbers from a list using Predicate and lambda.
  4. Use Consumer to print each element of a list with uppercase.
  5. Create a Supplier that generates random numbers (1–100).
  6. Use Function to convert a list of strings to uppercase.
  7. Chain Function: Convert string to uppercase, then to length.
  8. Use Predicate to filter names starting with "A" from a list.
  9. Implement a custom functional interface "Calculator" with add and subtract methods using lambda.
  10. Use lambda with Runnable to print "Hello from thread".
  11. Sort a list of employees by salary using Comparator lambda.
  12. Group a list of strings by length using Collectors.groupingBy with lambda.
  13. Use Optional with lambda: If value present, print it; else print default.
  14. Create a Predicate that checks if a number is prime (using lambda).
  15. Use lambda with Stream: Filter names starting with "A", map to uppercase, collect to list.
  16. Implement a method reference for static method (e.g., String::toUpperCase).
  17. Use constructor reference to create a new ArrayList.
  18. Handle checked exception in lambda (wrap in try-catch or custom functional interface).
  19. Use BiFunction to add two numbers with lambda.
  20. Combine Predicate and Consumer: Filter and print only matching elements.