Interview Prep Zoneby CuriouserLabs

Question 4 of 5 · staff level

How do lambdas work under the hood? Functional interfaces, capture rules and invokedynamic.

🎤 Say this first

A lambda is compiled to a private static/instance method plus an invokedynamic call site; at first invocation the LambdaMetafactory spins a lightweight class implementing the functional interface — no anonymous-class file per lambda, and stateless lambdas are cached singletons. Captured locals must be effectively final because they're copied into the lambda (there is no shared frame). this inside a lambda is the enclosing instance (unlike anonymous classes), and method references come in four flavors.

The full picture

  • Functional interface = exactly one abstract method (SAM); @FunctionalInterface enforces it. Know the core set: Function, BiFunction, Supplier, Consumer, Predicate, UnaryOperator + primitive specializations (IntFunction, ToLongFunction…) that avoid boxing.
  • Capture semantics: locals are captured by value (hence effectively final); fields are read through the captured this — mutable and racy if you share the lambda across threads.
  • Method reference forms: Type::staticMethod, instance::method, Type::instanceMethod (receiver becomes first arg), Type::new.
  • invokedynamic benefit: the JVM can optimize dispatch and avoid class-file bloat; capturing lambdas allocate, non-capturing ones don't — relevant in hot paths.
Two gotchas seniors call out
// 1. 'this' differs from anonymous classes
Runnable r = () -> System.out.println(this);      // enclosing object
Runnable r2 = new Runnable() {
    public void run() { System.out.println(this); } // the anonymous instance
};

// 2. Boxing in disguise on hot paths
Function<Integer,Integer> f = x -> x * 2;   // boxes every call
IntUnaryOperator g = x -> x * 2;            // primitive, no allocation

🔄 Likely follow-up questions

  • Why must captured variables be effectively final — what bug does it prevent?
  • What does a capturing lambda cost vs a non-capturing one?
  • How do checked exceptions interact with the standard functional interfaces (and your options)?