How does Java infer the types of a lambda's parameters, and when would you write them explicitly or with `var`?
answer
- types inferred from the target interface's single method
- three styles: inferred / explicit / var
- all-or-nothing — no mixing
- explicit or var ⇒ parentheses required even for one param
- var's killer use: an annotation slot
basics
~20 sJava usually figures out a lambda's parameter types from the functional interface it's being matched to, so you can leave them off: (a, b) -> a + b. You can write them explicitly like (int a, int b) or use (var a, var b) — but all parameters must use the same style.
solid answer
~40 sOnce target typing fixes the functional interface, the compiler reads that interface's single abstract method to infer each lambda parameter's type, so you normally omit them: `Comparator<String> c = (a, b) -> a.compareTo(b);` infers both as `String`. You may instead write **explicit** types — `(String a, String b) ->` — or use **`var`** parameters (Java 11+) — `(var a, var b) ->`. The choice is **all-or-nothing**: every parameter must be inferred, all explicit, or all `var`; you cannot mix styles. Explicit or `var` parameters always require parentheses, even for one parameter. The main reason to use explicit or `var` types is to **attach annotations** (e.g. `(@NonNull var x) ->`) or to improve readability in a complex lambda; `var` also keeps the inference benefit while giving a syntactic slot for annotations.
go deeper
Knows parameter types can usually be omitted and the lambda still compiles.
Can state where inferred types come from, the all-or-nothing rule, and the parentheses requirement for explicit/var params.
Explains the concrete motivations for var parameters (annotations, readability) and how parameter typing interacts with target typing and overloads.
Guides team style on when explicit typing aids maintainability versus noise, and understands the language-design reasons for the all-or-nothing constraint.
## Where parameter types come from A lambda's parameter types are normally **inferred**, not written. The chain is: target typing fixes the **functional interface**, the compiler looks at that interface's **single abstract method**, and copies its parameter types onto the lambda. Example: ```java Comparator<String> byLen = (a, b) -> a.length() - b.length(); ``` `Comparator<String>` has `int compare(String, String)`, so `a` and `b` are inferred as `String` — you call `String` methods on them with no cast. ## Three ways to write parameters 1. **Inferred (the default):** `(a, b) -> ...` — most concise, used most of the time. 2. **Explicit types:** `(String a, String b) -> ...` — you state the types yourself. Useful for documentation or when inference is hard to read. 3. **`var` parameters (Java 11+):** `(var a, var b) -> ...` — the type is still inferred, but each parameter has a `var` keyword. The point of `var` here is **uniformity with local-variable syntax** and, crucially, a **place to hang annotations**. ## The all-or-nothing rule Within a single lambda you must pick one style for **all** parameters: - ✅ `(a, b) -> ...` (all inferred) - ✅ `(int a, int b) -> ...` (all explicit) - ✅ `(var a, var b) -> ...` (all var) - ❌ `(int a, b) -> ...` (mix of explicit + inferred) - ❌ `(var a, int b) -> ...` (mix of var + explicit) This rule keeps the grammar unambiguous. ## Parentheses requirement With inferred types, a single parameter can drop the parentheses: `x -> x.trim()`. But the moment you write an explicit type or `var`, the parentheses become **mandatory**, even for one parameter: `(String x) -> x.trim()` and `(var x) -> x.trim()`. There is no way to write `var x -> ...` or `String x -> ...`. ## Why use explicit or var types at all? Most code uses inference. You reach for explicit or `var` parameters when: - **Annotations** are needed on a parameter — annotations require a type slot: `(@Nonnull var s) -> s.trim()`. - **Readability** — in a long or generically nasty lambda, naming the type can help the reader. - **Disambiguating overloads** — occasionally explicit types steer overload resolution. `var` is the sweet spot when you want the annotation slot without re-stating a verbose generic type. ## Common pitfalls - Mixing styles → compile error. - Dropping parentheses with explicit/`var` types → compile error. - Assuming the type must be written: in the vast majority of cases, omitting it is correct and idiomatic.
- Why might you choose `(var a, var b) -> ...` over `(a, b) -> ...` when both infer the same types?`var` gives each parameter a syntactic slot for annotations (e.g. `(@Nonnull var a, var b) -> ...`), which the bare inferred form cannot carry. It can also read more uniformly with local-variable style. Otherwise the bare form is more concise.
- Is `(int x) -> x * 2` legal, and is `int x -> x * 2`?`(int x) -> x * 2` is legal — explicit types require parentheses. `int x -> x * 2` is illegal: without parentheses you may only use a single inferred parameter.
saying these in an interview costs you the question
- Mixing inferred and explicit/var parameter types in one lambda
- Writing `var x -> ...` or `String x -> ...` without parentheses
- Thinking `var` parameters defeat inference (they don't — the type is still inferred)
- Believing you must always write parameter types explicitly