How does the compiler decide which functional interface a lambda or method reference implements, and why can the same lambda satisfy multiple functional interfaces?
answer
- Lambda = poly expression: typed by context (target type)
- Target comes from assignment / argument / return / cast
- No target (var) or non-functional target (Object) => compile error
- Match is structural: params + return + throws vs the SAM
- Overloaded methods can be ambiguous -> cast to disambiguate
basics
~20 sA lambda has no type of its own. The compiler looks at the context where the lambda is used — the variable type, the method parameter, or the return type — to find the expected functional interface, then checks the lambda matches that interface's single method. The same lambda fits any interface whose method has a compatible shape.
solid answer
~50 sA lambda is a poly expression: it has no standalone type and acquires one from its target context — an assignment, a method argument, a cast, or a return statement. The compiler finds the expected functional interface from that context, then checks that the lambda's parameters and return are compatible with that interface's single abstract method. Because matching is by shape (parameter and return types), one lambda like (a, b) -> a + b can implement any functional interface with two compatible parameters returning a compatible value — for instance a custom Calculator or BinaryOperator<Integer>. This is why a lambda cannot be assigned to var without a target, and why it cannot be assigned to Object. With overloaded methods, the lambda's shape participates in overload resolution and can be ambiguous, requiring an explicit cast. Method references work the same way: ::method is resolved against the target functional interface's signature. The single-abstract-method requirement is what makes this unambiguous within one interface.
go deeper
Knows a lambda implements a functional interface and that the variable/parameter type tells you which one.
Explains target typing from assignment/argument/return and that a lambda can't go to Object or bare var; matches by parameter/return shape.
Reasons about poly expressions, structural matching, generic inference from the target, and resolves overload ambiguity with casts; explains method-reference resolution.
Designs overload sets and APIs to avoid lambda ambiguity, understands the JLS poly-expression rules' impact on API evolution and source compatibility, and guides library ergonomics around target typing.
## The core puzzle Consider: ```java Runnable r = () -> System.out.println("hi"); Callable<Void> c = () -> { System.out.println("hi"); return null; }; ``` The expression `() -> ...` looks similar in both lines yet becomes a `Runnable` in one and a `Callable` in the other. How? Because **a lambda has no intrinsic type.** Its type is determined entirely by **where it is used**. This is called being a **poly expression** — an expression whose type depends on context (its **target type**). ## What provides the target type The compiler looks for the **target type** in these contexts: 1. **Variable assignment** — `Runnable r = () -> ...;` → target is `Runnable`. 2. **Method argument** — `executor.submit(() -> ...)` → target is the parameter type of `submit`. 3. **Return statement** — `return () -> ...;` → target is the method's declared return type. 4. **Cast** — `(Comparator<String>) (a, b) -> ...` → target is the cast type. 5. **Array initializer / conditional / lambda body** — and a few more. If none of these supplies a functional-interface target, the lambda **does not compile**. That is why: ```java var x = () -> 42; // ERROR: no target type for the lambda Object o = () -> 42; // ERROR: Object is not a functional interface ``` `Object` is not a functional interface (it has many methods, none of which a lambda could be), so it cannot be a target. ## How the match is checked Once the compiler knows the target is, say, `Function<String, Integer>`, it does **shape matching** against that interface's single abstract method `Integer apply(String)`: - The lambda's **parameter count** must match (here, one). - Each parameter type must be assignment-compatible (or inferred from the interface). - The lambda's **return** must be compatible with the method's return type. - The lambda's **thrown exceptions** must be compatible with the method's `throws` clause. Because a functional interface has **exactly one** abstract method, there is exactly one signature to match — no ambiguity *within* a single interface. This is precisely why the SAM rule exists: with two abstract methods the compiler couldn't know which one the lambda implements. ## Why one lambda fits many interfaces Matching is **structural** (by shape), not nominal (by name). So `(a, b) -> a + b` can implement: ```java interface Calculator { int compute(int x, int y); } Calculator calc = (a, b) -> a + b; // ok BinaryOperator<Integer> op = (a, b) -> a + b; // ok (different interface, same shape) ``` The lambda is not "a Calculator" or "a BinaryOperator" inherently — it becomes whichever the context demands. ## Overload resolution and ambiguity When a lambda is passed to an **overloaded** method, the lambda's shape participates in choosing the overload. If two overloads accept different functional interfaces that the lambda could satisfy, the call is **ambiguous** and fails to compile; you disambiguate with an explicit cast: ```java void run(Runnable r) {} void run(Callable<String> c) {} run(() -> "x"); // AMBIGUOUS if both could match run((Callable<String>) () -> "x"); // explicit cast resolves it ``` ## Method references A **method reference** (`String::length`, `System.out::println`, `ArrayList::new`) is resolved the same way: the compiler takes the target functional interface's abstract-method signature and checks that the referenced method (or constructor) can satisfy it. `String::length` works as a `Function<String,Integer>` because calling `.length()` on a `String` yields an `int`. ## Generics and inference Target typing also drives **type inference** for generic functional interfaces. In `Function<String,Integer> f = s -> s.length();`, the compiler infers that `s` is a `String` from the target `Function<String,Integer>`, so you can call `String` methods on it without declaring the type. ## Summary A lambda/method reference is a poly expression typed by its surrounding context. The compiler finds the target functional interface, matches the lambda's shape (params, return, throws) against that interface's lone abstract method, and infers generic types from the target. Structural matching is why one lambda can satisfy many interfaces, and overload ambiguity is resolved with an explicit cast.
- Why does 'var x = () -> 1;' fail to compile?A lambda is a poly expression with no standalone type; it needs a target functional-interface type from context. var has no explicit target type to give the lambda, so the compiler cannot infer one and reports an error.
- How do you resolve an ambiguous overloaded call where two overloads both accept a matching functional interface?Cast the lambda to the intended functional interface type at the call site, e.g. run((Callable<String>) () -> "x"), which fixes the target type and selects the overload.
saying these in an interview costs you the question
- Saying a lambda has a fixed type independent of context
- Thinking you can assign a lambda to Object or var without a target type
- Believing the lambda is matched by interface name rather than method shape
- Assuming overloaded calls with lambdas are never ambiguous