Think of it as a one-job tool: interviewers ask this because it is the doorway to lambdas, method references, and cleaner Java 8 code.
Question: What is a Functional Interface?
Answer: A functional interface is an interface that has exactly one abstract method, so it represents one behavior or action. It can still have default methods, static methods, and in newer Java versions private helper methods, because those do not count toward the single abstract method rule. In Java 8, functional interfaces are the target type for lambda expressions and method references.
Interview-Ready Answer: A functional interface is an interface with exactly one abstract method, often called a SAM interface, which stands for Single Abstract Method. I use it when I want to pass behavior into a method, especially with lambdas or method references in Java 8. The best-known examples are `Runnable`, `Callable`, `Predicate`, `Function`, and `Consumer`. The `@FunctionalInterface` annotation is optional, but I like it because it makes the compiler enforce the rule for me.
Detailed Explanation: A functional interface is a contract for behavior, not for data. It says, in simple terms, here is one operation you must provide. That one operation can be implemented with a lambda, an anonymous class, or a method reference. The key idea is that Java can treat the interface like a type for a piece of behavior.
| Aspect | Functional Interface | Regular Interface |
|---|---|---|
| Abstract methods | Exactly one | One or more |
| Lambda use | Yes | No |
| Main purpose | Behavior injection | General contract |
| Best fit | Callbacks, streams | Broader APIs |
One job, one button: if an interface has one real job, Java can turn it into a lambda.
Real-World Example: Imagine a checkout service in an e-commerce app. The team has a pricing pipeline where one rule checks coupon eligibility, another applies a discount, and another blocks suspicious carts. Each rule is passed around as a functional interface like `Predicate<Cart>` or `Function<Cart, Price>`, so the checkout flow can swap rules without rewriting the pipeline.
Now picture a bad change: a developer thinks the interface should also expose a debug label, so they add a second abstract method. Suddenly every lambda-based rule in the service stops compiling, CI turns red, and the release for a big weekend promotion is delayed. The compiler message points right at the issue, but the business impact is visible immediately: marketing cannot launch the coupon campaign, support gets tickets saying discount application is temporarily unavailable, and the team scrambles to separate metadata from behavior.
The lesson is simple: keep the functional interface focused on one job only. Put extra metadata in another class or enum, not in the SAM itself.
import java.util.Objects;
import java.util.function.Predicate;
public class Main {
@FunctionalInterface
interface StringTransformer {
String apply(String input);
// Default methods do not break the SAM rule; they are reusable helpers.
default StringTransformer andThen(StringTransformer after) {
Objects.requireNonNull(after, "after transformer must not be null");
return input -> after.apply(apply(input));
}
static StringTransformer identity() {
return input -> input;
}
}
private static String transform(String input, StringTransformer transformer) {
// We fail fast because a missing strategy is a programmer error, not a valid business case.
Objects.requireNonNull(input, "input must not be null");
Objects.requireNonNull(transformer, "transformer must not be null");
return transformer.apply(input);
}
public static void main(String[] args) {
// A lambda works because StringTransformer has exactly one abstract method.
StringTransformer trim = s -> s.trim();
// A method reference is just a shorter lambda when the signature already matches.
StringTransformer upper = String::toUpperCase;
StringTransformer trimThenUpper = trim.andThen(upper);
System.out.println("1) " + transform(" hello ", trim));
System.out.println("2) " + transform(" hello ", trimThenUpper));
System.out.println("3) " + transform("java", upper));
System.out.println("4) " + transform("same", StringTransformer.identity()));
// Built-in functional interfaces follow the same rule. Predicate<T> is also a SAM type.
Predicate<String> nonBlank = s -> s != null && !s.trim().isEmpty();
System.out.println("5) coupon valid? " + nonBlank.test(" SAVE10 "));
System.out.println("6) coupon valid? " + nonBlank.test(" ")); // edge case: blank text
// Failure path: null strategy. This is the kind of bug a helper method should catch early.
try {
System.out.println(transform("oops", null));
} catch (NullPointerException e) {
System.out.println("7) failed fast: " + e.getMessage());
}
}
}Follow-up & Tricky Questions:
Common Mistakes:
Memory Hook: One job, one button. If the interface has one real job, it can be powered by a lambda.
Cheat Sheet:
Practice Tasks: