RadhaPublication
HomeInterviewLearning PathsRole TracksMockBooksPricing
Login / Sign UpLog in

© 2026 Radha Publication. All rights reserved.

Shop on AmazonDonateContactPrivacyTerms
Java questions
MediumJava#576 min readJul 11, 2026

Function interface.

practice
learning
Practice modeTest yourself instead of reading straight through

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.

🧠 Memory Map
Memory map — visual summary of this topic

What it is

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.

How it works under the hood

  1. You declare a Function reference, usually with a lambda like s -> s.length() or a method reference like String::length.
  2. The compiler checks that your lambda matches the single abstract method apply(T).
  3. At runtime, the JVM links the lambda to a small implementation behind the scenes; you write a lambda, but the program gets an object that can be invoked like any other function.
  4. When you call apply, the input is passed into your transformation code and the returned result flows to the next step.
  5. If you chain functions with 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.

When and why to use it

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.”

Comparison with nearby interfaces

InterfaceInputOutputUse case
Function11Transform
Predicate1booleanTest
Consumer10Use
Supplier01Provide
UnaryOperator11Same type
BiFunction21Combine

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>.

Performance and edge cases

  • Time complexity: one call to apply is usually O(1), but the real cost is whatever your lambda does.
  • Space complexity: the function object itself is tiny; stateless lambdas are often reused efficiently. Capturing lambdas, which close over local values, may need a small object to store that captured state.
  • Boxing: Function<Integer,Integer> may create wrapper objects. For primitives, prefer specializations like IntUnaryOperator to avoid boxing overhead.
  • Nulls: apply(null) is legal only if your function handles it. The interface does not protect you from null input.
  • Order matters: 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.

Java
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:

  • What is the difference between 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.
  • What does @FunctionalInterface do?
    It tells the compiler to enforce the “one abstract method” rule. It is not required for lambdas to work, but it is a helpful safety net and documentation for humans.
  • Can a Function return null?
    Yes, technically it can, but it often makes pipelines fragile. In modern Java, returning Optional or using a non-null contract is usually safer if “no value” is a real case.
  • What happens if the input is null?
    The interface itself does nothing special; the lambda decides. Some functions reject null immediately, while others deliberately handle it, so you must define the behavior.
  • When would you use UnaryOperator instead?
    When the input and output types are the same. It is still a Function, but it communicates intent more clearly and makes the API easier to read.
  • Tricky: Is f.andThen(g) the same as f.compose(g)?
    No. andThen means “do f, then g”; compose means “do g, then f.” This order difference is one of the most common mistakes.
  • Tricky: Can a lambda capture local variables and then modify them?
    No. Captured local variables must be effectively final, meaning they are assigned once and not changed afterward. This rule keeps lambda behavior predictable and avoids confusing state bugs.
  • Tricky: Is a method reference different from a lambda?
    A method reference is just a shorter syntax for a lambda that calls an existing method. For example, String::trim and s -> s.trim() express the same idea.

Common Mistakes:

  • Thinking Function is only for streams. Correction: it is a general-purpose transformation tool used anywhere you want reusable input-to-output logic.
  • Confusing compose and andThen. Correction: memorize the order with “then goes forward, compose goes backward.”
  • Using Function<Integer, Integer> for heavy primitive work. Correction: prefer primitive specializations like IntUnaryOperator to avoid boxing.
  • Assuming 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.
  • Single abstract method: apply(T).
  • andThen = g(f(x)) order.
  • compose = f(g(x)) order.
  • Use identity() for a no-op function.
  • Prefer primitive specializations for performance on primitive data.

Practice Tasks:

  • Write a Function<String, String> that trims and lowercases names.
  • Chain a Function<String, Integer> with a second function that doubles the result.
  • Replace a small for-loop that transforms a list with stream().map(...) and a Function.
Previous
Back to Questions←→to navigate
Next

Recommended Resources

Recommended Book

Cracking the Coding Interview

189 Programming Questions and Solutions

Recommended Book

Cracking the Coding Interview

189 Programming Questions and Solutions

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); } }