Lambda Expressions and Functional Interfaces in Java
Fast recall
Quick revision
Core answer
parameters -> body. The compiler uses type inference from the target type (the variable, parameter, or return type the lambda is assigned to) to figure out which functional interface the lambda implements. Under the hood, lambdas are implemented via `invokedynamic` and the `LambdaMetafactory` — the JVM defers choosing an implementation strategy until runtime, typically generating a lightweight class on demand rather than an anonymous class at compile time. This makes lambdas faster and lower-memory than anonymous classes for equivalent code. The standard functional interfaces live in java.util.function — Function, Predicate, Consumer, Supplier, and their binary/unary/int/long/double specializations.Whether you understand lambdas are just syntactic sugar for a functional interface implementation, know the standard functional interfaces, and can discuss how they're implemented under the hood
Thinking lambdas are compiled to anonymous classes (they aren't since Java 8 — they use invokedynamic); confusing lambdas with method references
Explain invokedynamic and LambdaMetafactory, mention capture semantics (effectively final), and discuss the performance difference vs anonymous classes
Interview-ready guide
Interview answer
A functional interface is any interface with exactly one abstract method — Runnable, Comparator, Callable, Function, Predicate, and so on. The @FunctionalInterface annotation documents intent and enforces the single-abstract-method property.
A lambda expression is a compact syntax for implementing a functional interface inline. (a, b) -> a.length() - b.length() is a lambda; the compiler uses target typing to figure out from context which functional interface it implements. In the same line of code, that same expression could satisfy a Comparator or a BiFunction — target type decides.
The standard functional interfaces live in java.util.function. The big four are Function, Predicate, Consumer, and Supplier. Plus Bi- variants for two inputs, UnaryOperator and BinaryOperator for same-type transformations, and primitive specializations like IntFunction or ToIntFunction that avoid boxing — important in hot paths over millions of elements.
Under the hood: lambdas are not compiled to anonymous classes, contrary to a common misconception. The compiler emits an `invokedynamic` instruction that calls LambdaMetafactory at runtime. The metafactory decides on a strategy — typically generates a lightweight class via java.lang.invoke and caches it. The payoff: stateless lambdas are singletons — () -> 42 allocates exactly once ever — and the JIT can inline the lambda body into the call site, eliminating dispatch overhead. Benchmarks show lambdas about 25% faster than equivalent anonymous classes.
One important constraint: captured local variables must be effectively final — assigned once, never reassigned. The restriction exists because lambdas may outlive the enclosing method (stored in fields, submitted to executors), and mutable captured state would require expensive synchronization. For genuine mutation, use AtomicInteger or accumulate via reduce/collect rather than mutating in forEach.
Lambda Expressions and Functional Interfaces in Java
Direct Answer
A lambda expression is a concise syntax for implementing a functional interface — an interface with exactly one abstract method. Instead of writing an anonymous class, you write parameters -> body. The compiler uses type inference from the target type (the variable, parameter, or return type the lambda is assigned to) to figure out which functional interface the lambda implements. Under the hood, lambdas are implemented via invokedynamic and the LambdaMetafactory — the JVM defers choosing an implementation strategy until runtime, typically generating a lightweight class on demand rather than an anonymous class at compile time. This makes lambdas faster and lower-memory than anonymous classes for equivalent code. The standard functional interfaces live in java.util.function — Function<T, R>, Predicate<T>, Consumer<T>, Supplier<T>, and their binary/unary/int/long/double specializations.
Common Mistakes & Pitfalls
Thinking lambdas are compiled to anonymous classes (they aren't since Java 8 — they use invokedynamic); confusing lambdas with method references
Check your understanding
Answer these without looking back at the article.
- What's the difference between `Function<T, R>` and `BiFunction<T, U, R>`?
- How does `invokedynamic` differ from a regular `invokevirtual` call?
- Why are primitive-specialized functional interfaces like `IntFunction` needed?
- What happens if you try to capture a variable that's been reassigned?
Related interview prep categories
1Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.