Hook: Interviewers love this question because Function is the doorway from old-style looping to clean, fluent Java 8 code.
Question: What is the Function interface in Java?
Answer: Function<T,R> is a functional interface that takes one value of type T and returns one value of type R. Its single abstract method is apply(T t), so it is perfect for lambdas and method references that transform data. In practice, you use it when you want to map one value into another, like trimming a string, parsing text into a number, or converting a domain object into a DTO.
Interview-Ready Answer: I would say that Function<T,R> is Java’s standard “input to output” interface. It has one abstract method, apply, so I can implement it with a lambda or method reference. I use it when I need to transform values, and I especially like its default methods andThen and compose because they let me chain transformations cleanly without writing extra glue code.
Detailed Explanation: In Java 8, a functional interface means an interface with exactly one abstract method. The Function<T,R> interface lives in java.util.function and models a transformation: one input in, one output out. The word generic means the types are parameterized, so T and R can be anything: String to Integer, User to UserDto, and so on.
Function reference, usually with a lambda like s -> s.length() or a method reference like String::length.apply(T).apply, the input is passed into your transformation code and the returned result flows to the next step.andThen, the first result becomes the second input. If you use compose, the order is reversed: the new input is transformed first, then your original function runs.Use Function when your code needs a reusable transformation. Common examples are stream pipelines like map, validation/normalization steps, converting API models, and building small reusable utilities. It keeps code readable because the intent is “transform this into that” instead of “loop, mutate, assign, repeat.”
| Interface | Input | Output | Use case |
|---|---|---|---|
Function | 1 | 1 | Transform |
Predicate | 1 | boolean | Test |
Consumer | 1 | 0 | Use |
Supplier | 0 | 1 | Provide |
UnaryOperator | 1 | 1 | Same type |
BiFunction | 2 | 1 | Combine |
Quick rule: if you are changing one thing into another, start with Function. If the input and output types are the same, UnaryOperator<T> is a more precise subtype of Function<T,T>.
apply is usually O(1), but the real cost is whatever your lambda does.Function<Integer,Integer> may create wrapper objects. For primitives, prefer specializations like IntUnaryOperator to avoid boxing overhead.apply(null) is legal only if your function handles it. The interface does not protect you from null input.f.andThen(g) means g(f(x)), while f.compose(g) means f(g(x)). This is a classic interview trap.Memory hook: think of Function as a factory conveyor belt: raw material enters on one side, finished product comes out the other. compose is the belt running backward; andThen is the belt running forward.
Real-World Story: Imagine a checkout service in an e-commerce app. You receive a price string like " 199.99 ", trim it, parse it, apply a discount, and then round for display. Each of those steps is a small transformation, so a Function chain keeps the checkout code short and testable.
If a developer mixes up compose and andThen, the checkout can calculate tax on the wrong amount or apply discount rules in the wrong order. The bug shows up as small but annoying price mismatches, customer support tickets, and logs where the final amount does not match the expected pipeline. In production, that can mean refunds, failed reconciliation, and a finance team asking why totals differ by a few cents across orders.
Common failure pattern: the code looks elegant, but one transformation assumes a trimmed string while another still sees whitespace. The symptom is often a NumberFormatException or a subtle business error, not a compiler error. That is why interviewers like this topic: it tests both language knowledge and practical judgment about order, null handling, and safe chaining.
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
public class FunctionInterfaceDemo {
public static void main(String[] args) {
// A Function is a reusable transformation: one input, one output.
Function<String, String> trim = String::trim;
Function<String, String> toUpper = String::toUpperCase;
Function<String, String> trimThenUpper = trim.andThen(toUpper);
System.out.println("1) andThen: ' java ' -> '" + trimThenUpper.apply(" java ") + "'");
// compose reverses the order: the argument function runs first.
Function<String, String> upperThenTrim = trim.compose(toUpper);
System.out.println("2) compose: ' java ' -> '" + upperThenTrim.apply(" java ") + "'");
// identity returns the same value. Useful when a pipeline needs a no-op function.
Function<String, String> identity = Function.identity();
System.out.println("3) identity: 'hello' -> '" + identity.apply("hello") + "'");
// Transform String -> Integer, then Integer -> String.
Function<String, Integer> parseInt = s -> {
if (s == null) {
throw new IllegalArgumentException("input cannot be null");
}
return Integer.parseInt(s.trim());
};
Function<Integer, Integer> square = x -> x * x;
Function<String, Integer> parseThenSquare = parseInt.andThen(square);
System.out.println("4) parseThenSquare: ' 12 ' -> " + parseThenSquare.apply(" 12 "));
// Edge case: blank or non-numeric input fails cleanly.
for (String input : Arrays.asList("7", " ", "abc", null)) {
try {
Integer result = parseThenSquare.apply(input);
System.out.println("5) input='" + input + "' => " + result);
} catch (Exception ex) {
System.out.println("5) input='" + input + "' => ERROR: " + ex.getClass().getSimpleName() + " - " + ex.getMessage());
}
}
// Functions work naturally with streams.
List<String> names = Arrays.asList(" alice ", "bob", " carol");
List<String> cleaned = names.stream()
.map(trimThenUpper)
.toList();
System.out.println("6) stream map: " + cleaned);
}
}Follow-up & Tricky Questions:
Function and BiFunction? Function takes one input; BiFunction takes two. If you need to combine two values, like (price, taxRate) -> total, use BiFunction rather than forcing a pair into one object too early.@FunctionalInterface do? Function return null? Optional or using a non-null contract is usually safer if “no value” is a real case.null? UnaryOperator instead? Function, but it communicates intent more clearly and makes the API easier to read.f.andThen(g) the same as f.compose(g)? andThen means “do f, then g”; compose means “do g, then f.” This order difference is one of the most common mistakes.String::trim and s -> s.trim() express the same idea.Common Mistakes:
Function is only for streams. Correction: it is a general-purpose transformation tool used anywhere you want reusable input-to-output logic.compose and andThen. Correction: memorize the order with “then goes forward, compose goes backward.”Function<Integer, Integer> for heavy primitive work. Correction: prefer primitive specializations like IntUnaryOperator to avoid boxing.null is handled automatically. Correction: define null behavior explicitly inside the lambda or reject it early.Memory Hook: “Function is a value converter.” One box goes in, one box comes out. andThen is left-to-right; compose is right-to-left.
Cheat Sheet:
Function<T,R> = transform T into R.apply(T).andThen = g(f(x)) order.compose = f(g(x)) order.identity() for a no-op function.Practice Tasks:
Function<String, String> that trims and lowercases names.Function<String, Integer> with a second function that doubles the result.stream().map(...) and a Function.