Why lambda questions matter
Lambdas are the gateway to modern, functional-style Java — streams, comparators, callbacks and event handlers all lean on them. Interviewers ask about lambdas to check that you understand what they actually are (an implementation of a single-method interface), not just that you can copy the arrow syntax. The revealing questions are about capture and this binding, because those expose whether you understand the mechanics or only the appearance.
This page covers the lambda questions asked most. Since a lambda always targets a functional interface, read it alongside the functional interfaces questions; for the type machinery underneath, the generics set is a useful companion.
Q1. What is a lambda expression, really?
A lambda is a compact implementation of a functional interface — an interface with exactly one abstract method. It is not a standalone function; the compiler infers a target type from context (the "SAM" interface) and the lambda supplies that single method's body. (a, b) -> a + b is an implementation of something like BinaryOperator<Integer>.
Runnable r = () -> System.out.println("run"); // target type: Runnable
Comparator<String> byLen = (a, b) -> a.length() - b.length();
The point to make: the same lambda can implement different interfaces depending on the target type, which is why context is required. Saying "a lambda has no type on its own — its type is the functional interface it is assigned to" is the answer that shows real understanding.
Q2. What variables can a lambda capture?
A lambda can read local variables that are final or effectively final (assigned exactly once), and can freely access instance fields, static fields and this. It cannot reassign a captured local, because the capture is by value of an effectively-final variable — mutating it would be ambiguous once the lambda outlives the method.
int base = 10; // effectively final — never reassigned
Function<Integer, Integer> addBase = x -> x + base; // OK to read
// base = 20; // would break it: base is no longer effectively final
int[] counter = {0}; // workaround: mutate the array's contents, not the variable
Runnable inc = () -> counter[0]++; // legal — 'counter' reference is effectively final
The nuance interviewers probe: you can mutate the object a captured reference points to (the array trick), because the variable stays effectively final even though its target changes. Explaining that distinction — final variable versus mutable object — is the depth they want.
Q3. How is a lambda different from an anonymous inner class?
Beyond brevity, the big difference is this. Inside a lambda, this refers to the enclosing instance; inside an anonymous class, this refers to the anonymous object itself. A lambda also introduces no new scope for variable shadowing and is compiled via invokedynamic rather than emitting a separate class file.
class Widget {
Runnable asLambda = () -> System.out.println(this.getClass()); // Widget
Runnable asAnon = new Runnable() {
public void run() { System.out.println(this.getClass()); } // anonymous class
};
}
Interviewers use this to catch people who think a lambda is just "shorthand for an anonymous class." It is not: the this semantics differ, and lambdas can only target single-method interfaces, whereas an anonymous class can extend a class, add fields and hold state.
Q4. What are the four kinds of method reference?
Static (ClassName::staticMethod), bound instance (instance::method — a specific object), unbound instance (ClassName::instanceMethod — the receiver is the first argument), and constructor (ClassName::new). They are shorthand for a lambda whose entire body just calls one existing method.
Function<String, Integer> parse = Integer::parseInt; // static
Supplier<String> greet = "hello"::toUpperCase; // bound instance
Function<String, Integer> len = String::length; // unbound instance
Supplier<ArrayList<String>> make = ArrayList::new; // constructor
The one people confuse is bound versus unbound: "hi"::length is bound to a specific string, while String::length is unbound — the string comes in as the argument. Use a method reference only when the lambda forwards its parameters unchanged; otherwise a lambda is clearer.
Q5. Can a lambda have state or be recursive?
A lambda has no fields, so it cannot hold mutable state of its own — that is a defining difference from an anonymous class. It can appear recursive only through a field or a two-step assignment, because a lambda cannot reference the local variable it is being assigned to (that variable is not yet effectively final and, at that point, uninitialized).
The interview trap: Function<Integer,Integer> f = n -> n <= 1 ? 1 : n * f(n-1); does not compile because f is referenced before it is definitely assigned. The fix is to make f an instance/static field. If a question needs stateful behaviour, that is a signal to reach for a class or an anonymous class instead.
Q6. How do lambdas interact with checked exceptions?
A lambda can only throw checked exceptions that its target functional interface's method declares. Most standard functional interfaces (Function, Consumer, Supplier) declare no checked exceptions, so a lambda that performs I/O must catch and handle them inside, or you must use a custom functional interface that declares the exception.
This is a common real-world pain: stream.map(this::readFile) fails to compile if readFile throws IOException, because Function.apply declares none. The honest answer names the two options — wrap the checked exception in an unchecked one inside the lambda, or define a throwing functional interface — and notes that swallowing exceptions silently is the wrong fix.
Q7. Why does the JVM use invokedynamic for lambdas?
Lambdas compile to an invokedynamic call site rather than a generated inner-class file. At first execution, a bootstrap method (LambdaMetafactory) creates the implementation. This avoids emitting one .class per lambda at compile time, reduces startup class-loading, and lets the JVM choose the most efficient representation.
You do not need bytecode expertise here — the takeaway is that lambdas are lighter than anonymous classes because they defer implementation creation to runtime. Mentioning invokedynamic and LambdaMetafactory by name signals you have looked below the syntax, which is what an advanced follow-up is checking.
Q8. When should you not use a lambda?
When the logic is long or needs a name for readability, when you need state or multiple methods (use a class), when a method reference would be clearer, or when the lambda would capture and hide complex behaviour. A five-line lambda buried in a stream is usually a method waiting to be extracted.
Good taste is part of the answer: lambdas shine for small, pure, single-purpose behaviour passed to an API. If a reviewer would struggle to read it inline, extract it to a named method and pass a method reference. Showing that judgement — not just the ability to write lambdas — is what distinguishes a fluent answer.
How to prepare
Practice by rewriting anonymous-class callbacks as lambdas and feeling where it breaks — the this difference, the effectively-final rule, the checked-exception wall. Those friction points are exactly the interview questions. Then connect lambdas to the interfaces they implement in the functional interfaces set, and to the generic types (Function<T,R>, Predicate<T>) they are typed with in the generics questions. Rehearse explaining variable capture out loud, since "why can't I modify this counter in a lambda?" is one of the most common follow-ups a mock interview will throw at you.
Frequently Asked Questions
What is a lambda expression in Java?
What can a lambda capture from its surrounding scope?
How is a lambda different from an anonymous class?
What are method references?
Do lambdas create a new class at runtime?
Want to Build Your Career in Java Full Stack with AI?
Join CodeBegun and train with working industry engineers — Discover CodeBegun's Java Full Stack track

