Some languages model a callback as a one-method interface, while others have first-class function types. Compare the two encodings and explain what each one makes possible that the other does not.
answer
- nominal = named single-method type; structural = shaped function type
- Java: no function type, lambda takes the target SAM type
- same shape, different names -> incompatible in Java, fine in Kotlin
- C# delegates are nominal but multicast
- name carries semantics: ordering, retry policy, comparison
basics
~20 sA single-abstract-method interface is a nominal function type: it has a name, documentation, extra default helpers and overload identity, but two identically shaped ones are incompatible. Real function types — Kotlin's (A)->B, Go's func types, Swift closures — are interchangeable by shape but anonymous.
solid answer
~50 sBoth encode "a piece of behaviour passed as a value"; they differ in whether the type is **named or shaped**. - **Java** has no first-class function type. A lambda gets its type from the single-abstract-method interface expected at that position, so `Runnable` and a hand-written `interface Task { void run(); }` are incompatible despite identical shape, and you re-wrap with `r::run`. It also forces specialised variants such as `IntPredicate` to avoid boxing. - **Kotlin** has structural function types `(A) -> B`; `fun interface` exists mainly so a lambda can convert to a Java-facing SAM, and the conversion is explicit. - **C#** delegates are nominal too — `Action` and `Func` are named delegate types — but they are **multicast**, which no SAM interface is. - **Go**'s `func(T) error` is a structural named type, and a one-method interface is a different tool you pick when the receiver's identity or state matters. Use the nominal form when the **name** carries semantics — an ordering contract, not just a shape.
code
java · 5 linesinterface Task { void run(); }
Runnable r = () -> System.out.println("go");
// Task t = r; // does not compile: unrelated nominal types
Task t = r::run; // must re-wrapgo deeper
Know that a lambda in Java becomes an instance of the expected single-method interface, and that other languages have real function types instead.
Explain why identically shaped SAM types are incompatible, and name a language where the equivalent code needs no conversion.
Choose the encoding per API: named type when the contract's meaning exceeds its signature, function type when the shape is the contract, and know the boxing and combinator consequences.
Decide the vocabulary an API publishes across language boundaries, given that a nominal consumer cannot accept a structural producer without a wrapper at the edge.
## Two encodings of the same idea "Pass behaviour as a value" can be typed in two ways. **Nominal (single-abstract-method type).** You declare a named type with exactly one abstract operation, and a value of that type is a piece of behaviour. Java's functional interfaces work this way, as do C# delegates. The type has an identity beyond its shape: a name, documentation, possibly extra default helper methods, and the ability to participate in overload resolution. **Structural (function type).** The language has a type constructor for functions — Kotlin's `(A) -> B`, Go's `func(A) B`, Swift's `(A) -> B`, TypeScript's `(a: A) => B` — and any function with the right parameter and return types is a value of it. Nothing is declared. ## What the nominal encoding buys **Meaning.** A Java `Comparator<T>` is not just "a function from two Ts to an int". Its documentation carries a contract: total ordering, consistency, antisymmetry. Sorted collections rely on it. A bare function type has nowhere to hang that requirement, which is why even languages with real function types keep named single-method types for contracts whose semantics exceed their signature. **Extra members.** Because a functional interface is still an interface, it can carry default methods: `Comparator.thenComparing`, `Predicate.and`, `Function.andThen`. The combinators live on the type. In a structural function-type language they must be free functions or extension functions instead — Kotlin puts them in the standard library as extensions on function types. **Overload identity.** Two methods can accept differently named single-method types with the same shape and be distinguishable. With structural function types they collide. ## What the structural encoding buys **Interchangeability.** In Kotlin, a `(String) -> Int` produced anywhere is accepted anywhere that type is expected, with no adapter. In Java, a value of `interface Task { void run(); }` is not a `Runnable` even though the shapes are identical; you must re-wrap it as `r::run`. That friction is the whole reason `java.util.function` exists as a large fixed vocabulary that everyone is expected to reuse. **No boxing gymnastics from erasure.** Because Java's function-like types are ordinary generic interfaces and generics erase to references, `Predicate<Integer>` boxes; the JDK therefore ships hand-written specialisations `IntPredicate`, `LongFunction`, `DoubleUnaryOperator`, and so on. A language with a real function type still has to decide about specialisation, but the API surface does not fan out into dozens of near-duplicate named types. **Composition without ceremony.** Higher-order code — a function returning a function returning a function — types naturally, whereas nominal encodings need a named type per arity and shape. ## The middle ground and its seams Kotlin has both, and the seam is instructive. Function types are the native encoding; `fun interface` was added so a lambda can be converted to a Java-facing SAM type when calling into Java libraries. The conversion is explicit at the call site precisely because the two are not the same thing. C# sits in the nominal camp with a twist: `Action` and `Func<...>` are named delegate types, so nominally they are as rigid as Java's interfaces — but the language special-cases conversion between compatible delegate types, and delegates are **multicast**, meaning several handlers can be combined into one delegate value and invoked together. That capability has no equivalent in a SAM interface, which is why C# events are built on it. Go has both and keeps them separate on purpose: a `func(T) error` is used when the behaviour is all that matters, and a one-method interface is used when the receiver carries state or identity the caller may want to inspect or type-assert on. `http.HandlerFunc` is the bridge — a named function type with a method, so a plain function satisfies the `http.Handler` interface. ## Choosing - Use a **named single-method type** when the contract's meaning exceeds its signature (ordering, retry policy, comparison semantics), when you want combinators to hang off it, or when two same-shaped parameters must be distinguishable in overloads. - Use a **function type** when the shape is the whole contract, when values will be composed and passed through many layers, or when you are writing generic higher-order code. - In an API crossing language boundaries, remember that the nominal side cannot accept the structural side without a wrapper, so publish the named type and let callers convert once at the edge.
- Why does Java's standard library contain IntPredicate as well as Predicate?Because functional interfaces are ordinary generic interfaces and Java generics erase to references, so `Predicate<Integer>` boxes every argument. The primitive specialisations exist to avoid that in hot code. It is a direct consequence of encoding functions as nominal generic types rather than having a first-class function type with primitive-aware instantiation.
- What can a C# delegate do that a single-method interface cannot?Multicast. Several delegate values can be combined with += into one value that invokes all of them in order, which is the foundation of C# events. A one-method interface has exactly one implementation per value; combining handlers requires a composite object you write yourself, and the return value and exception semantics of the combination become your problem.
- Go has function types, so why does it still define interfaces with a single method?Because an interface value carries a receiver, which may hold state, be inspected with a type assertion, or implement further interfaces. `http.Handler` is an interface for exactly that reason, and `http.HandlerFunc` is a named function type with a ServeHTTP method that adapts a plain function into it — the two encodings coexisting deliberately in one API.
saying these in an interview costs you the question
- Claiming Java lambdas have their own type; they take the type of the single-method interface expected at that position.
- Assuming two identically shaped single-method interfaces are assignable to each other.
- Treating C# Func and Action as structural function types; they are named delegate types with special conversion rules.
- Saying function types are strictly better, ignoring that contracts like ordering have no place to live on a bare shape.
- Forgetting that combinators such as and/or/compose have to live somewhere — on the interface, or as free/extension functions.