Why functional interfaces come up constantly
Every lambda targets a functional interface, so this topic is the vocabulary of modern Java. Interviewers ask about it to confirm you can reach for the right built-in interface instead of inventing one, and that you understand how default methods turn these interfaces into composable building blocks. The questions are practical: given a use case, which interface, and how do you chain them.
This page covers the functional interface questions asked most. It pairs naturally with the lambda expressions questions, since lambdas implement these interfaces, and with the generics set, since the interfaces are generic types.
Q1. What makes an interface "functional"?
A functional interface has exactly one abstract method — the "single abstract method" or SAM. That single method is what a lambda or method reference implements. The interface may still declare any number of default and static methods, and re-declaring public methods of Object (like equals) does not count against the one-abstract-method rule.
The subtlety interviewers test: default and static methods do not count, and neither do abstract overrides of Object methods. So Comparator is functional despite declaring many default and static helpers, because it has just one genuinely abstract method, compare. Stating that rule precisely is the mark of a careful answer.
Q2. What does @FunctionalInterface actually do?
It is a compile-time check. Marking an interface @FunctionalInterface makes the compiler verify it has exactly one abstract method and fail the build otherwise. It is optional — one abstract method makes an interface functional regardless — but it documents intent and stops a teammate from accidentally adding a second abstract method and breaking every lambda that targets it.
@FunctionalInterface
interface Validator<T> {
boolean isValid(T value); // the single abstract method
default Validator<T> and(Validator<T> other) { // allowed: default method
return v -> this.isValid(v) && other.isValid(v);
}
// void reset(); // adding this would fail compilation
}
The value proposition is protection over time: without the annotation, an interface can silently stop being functional; with it, that mistake is caught immediately. Framing it as "guardrail, not requirement" is the answer.
Q3. Walk me through the core built-in functional interfaces.
Function<T,R> transforms a T into an R (apply). Predicate<T> tests a T and returns boolean (test). Consumer<T> takes a T and returns nothing (accept). Supplier<T> takes nothing and produces a T (get). UnaryOperator<T> and BinaryOperator<T> are Function/BiFunction specializations where input and output share a type.
Function<String, Integer> length = String::length; // T -> R
Predicate<String> isEmpty = String::isEmpty; // T -> boolean
Consumer<String> print = System.out::println; // T -> void
Supplier<LocalDate> today = LocalDate::now; // () -> T
BinaryOperator<Integer> sum = Integer::sum; // (T, T) -> T
Interviewers want you to map a use case to the right interface without hesitation: "produces a value from nothing" → Supplier, "side effect, no return" → Consumer, "boolean test" → Predicate. Knowing the two-argument (BiFunction, BiConsumer) and primitive (IntFunction, ToIntFunction, IntPredicate) variants exist rounds out the answer.
Q4. Why do primitive functional interfaces exist?
To avoid autoboxing. Function<Integer,Integer> boxes every int into an Integer, creating garbage in hot loops. Primitive specializations like IntUnaryOperator, ToIntFunction<T> and IntPredicate operate on primitives directly, so numeric-heavy code avoids the boxing cost.
The performance point is the whole answer: in a stream over millions of numbers, boxing dominates, which is why IntStream and the Int*/Long*/Double* functional interfaces exist. Reaching for ToIntFunction instead of Function<T,Integer> in a hot path is a small idiom that signals you think about allocation.
Q5. How do default methods enable composition?
Default methods on functional interfaces provide combinators that return a new composed interface. Function has andThen and compose; Predicate has and, or and negate; Consumer has andThen. They let you build complex behaviour from small pieces without writing wrapper classes.
Predicate<String> nonEmpty = s -> !s.isEmpty();
Predicate<String> shortEnough = s -> s.length() <= 10;
Predicate<String> valid = nonEmpty.and(shortEnough); // composed
Function<Integer,Integer> doubleIt = x -> x * 2;
Function<Integer,Integer> addOne = x -> x + 1;
doubleIt.andThen(addOne).apply(3); // 7 — double first (6), then add one
doubleIt.compose(addOne).apply(3); // 8 — add one first (4), then double
The andThen versus compose distinction is a favorite: andThen runs this function first, compose runs the argument first. Getting the order right on the spot demonstrates you have actually composed functions, not just read about it.
Q6. What is the difference between Predicate and a boolean-returning Function?
Predicate<T> returns a primitive boolean and provides logical composition (and, or, negate); Function<T,Boolean> returns a boxed Boolean object and has no logical combinators. Predicate is purpose-built for conditions — it avoids boxing, composes logically, and reads clearly — so it is the right choice for tests and filters.
Using Function<T,Boolean> where a Predicate belongs is a small code smell interviewers notice: you lose filter compatibility in streams and the and/or/negate helpers, and you pay for boxing. The reasoning — right tool for a boolean test — is what they are checking.
Q7. Can you define your own functional interface, and when should you?
Yes — annotate an interface with @FunctionalInterface and give it one abstract method. Do it when no built-in interface fits: you need a different arity, you need to declare a checked exception, or a domain-specific name makes the API clearer (Validator, RetryPolicy) than a generic Function.
@FunctionalInterface
interface ThrowingSupplier<T> {
T get() throws Exception; // declares a checked exception, unlike Supplier
}
The checked-exception case is the most practical reason: standard interfaces declare none, so wrapping I/O in a stream forces either try/catch inside the lambda or a custom throwing interface. Naming that concrete motivation shows you have hit the limitation in real code.
Q8. Is Runnable, Callable or Comparator a functional interface?
Yes — all three qualify. Runnable has one abstract method (run), Callable has one (call, which may throw), and Comparator has one genuinely abstract method (compare) despite its many default/static helpers. All three predate lambdas but became lambda targets automatically once the SAM rule was defined.
This question checks whether you understand that "functional interface" is a shape, not a special new category — many pre-Java-8 interfaces already had it. The insight to voice: lambdas worked retroactively with existing single-method interfaces, which is why new Thread(() -> ...) just works with the old Runnable.
How to prepare
Build a mental lookup table: no-input-produces-value → Supplier, input-no-output → Consumer, transform → Function, boolean test → Predicate, same-type transform → UnaryOperator. Then practice composition until andThen versus compose and Predicate.and/or/negate are instant. Connect this to the lambda expressions questions, since every lambda implements one of these interfaces, and to the generics set, which explains the type parameters. Rehearsing "which interface would you use for X?" out loud in a mock interview is the fastest way to make the mapping automatic.
Frequently Asked Questions
What is a functional interface?
What does the @FunctionalInterface annotation do?
What are the main built-in functional interfaces?
Can a functional interface have default methods?
What is the difference between Predicate and Function<T, Boolean>?
Want to Build Your Career in Java Full Stack with AI?
Join CodeBegun and train with working industry engineers — Explore the Java Full Stack program

