Effectively Final Variables in Lambdas
Fast recall
Quick revision
Core answer
final keyword, but the lambda captures the value of that local variable rather than sharing a mutable local-variable slot.Understanding of variable capture and why the JVM enforces the effectively-final rule
Saying the variable must be declared final — effectively final means the compiler infers it, no keyword needed
Explain that a lambda captures the local variable's value, while the object referenced by that value may still be mutable; thread safety is a separate issue
Interview-ready guide
Interview answer
When a lambda captures a local variable or parameter, that variable must be final or effectively final. Effectively final simply means it is not reassigned after its initial assignment; the final keyword is optional.
The lambda captures the local variable's value rather than sharing a local slot that surrounding code can later reassign. If the value is an object reference, both the lambda and surrounding code can still see the same object, so changing that object's contents is possible and may require normal thread-safety care.
For example, a lambda can read a List stored in an effectively-final variable and even add to that list, but it cannot assign a different list to the local variable. Writing count++ also counts as reassignment and prevents count from being captured. Fields follow different rules and may be read or written from the lambda, subject to ordinary access and concurrency rules.
Effectively Final Variables in Lambdas
Direct Answer
'Effectively final' means a local variable or parameter is never reassigned after its initial assignment. A lambda may capture it without an explicit final keyword, but the lambda captures the value of that local variable rather than sharing a mutable local-variable slot.
Code Example
import java.util.function.Function;
public class EffectivelyFinalDemo {
void demo() {
String greeting = "Hello";
Runnable print = () -> System.out.println(greeting);
final int multiplier = 3;
Function<Integer, Integer> triple = x -> x * multiplier;
int count = 0;
count++;
// Runnable broken = () -> System.out.println(count); // does not compile
int[] mutableCount = {0};
Runnable increment = () -> mutableCount[0]++; // legal, but mutable
increment.run();
}
}
The array example compiles because the captured reference is not reassigned; its array contents can still change. That is not a thread-safety technique. Concurrent updates would need an appropriate atomic or synchronised design.
Common Mistakes & Pitfalls
- Thinking you must write
final: Believing the variable needs thefinalkeyword. Effectively final means the compiler infers it — no keyword is needed as long as you never reassign. - Reassigning a captured variable: Updating a counter or accumulator like
total += xin the surrounding method, then wondering why the lambda no longer compiles. - Reaching for the array hack: Using a single-element array
int[] c = {0}to fake a mutable counter inside a lambda. It compiles, but it's a code smell and is unsafe across threads.
Check your understanding
Answer these without looking back at the article.
- Why can a lambda modify instance variables but not local variables?
- What is the single-element-array workaround and why is it discouraged?
- How does variable capture differ between lambdas and anonymous classes?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.