Java Module 9
Lambdas, functional interfaces and Optional interview questions
Interview questions on lambdas, functional interfaces and Optional: warm-ups, then what each harder question tests and how a good answer starts.
Warm-up questions
Short questions with short answers, each answered in full.
-
Fresher
What does effectively final mean, and why must local variables used in a lambda be effectively final?
Answered in the lesson Java lambda expressions and target typing
-
Fresher
What makes an interface a functional interface, and is the @FunctionalInterface annotation required?
Answered in the lesson Functional interfaces in java.util.function
-
Fresher
Name the four kinds of method references, with an example of each and the lambda it stands for.
Answered in the lesson Java method references: the four kinds
-
Fresher
What is a stable sort, and which Java sorting methods guarantee one?
Answered in the lesson Java comparators and multi-key sorting
-
Fresher
Why is calling get() on an Optional discouraged, and what should you call instead?
-
Fresher
What is a characterisation test, and why write one before turning a loop into a stream pipeline?
Answered in the lesson Refactoring Java loops to a functional style
Questions with model answers
What each question tests, the first step of a good answer and the follow-ups to expect.
-
Fresher to mid
A colleague counts invalid orders with int[] invalid = {0}; orders.forEach(o -> { if (!valid(o)) invalid[0]++; }), because a plain int would not compile. Review the change, and say what happens when forEach becomes orders.parallelStream().forEach.
- What it tests
- Why captured locals must be effectively final, why the one-element array only hides the problem, lost updates under a parallel stream, and side-effect-free alternatives.
- First step
- Explain the original error first: a lambda captures the value of a local variable, so the language forbids capturing one that changes. The array compiles because the variable now always points at the same array, but the lambda still changes shared state, so the change moved the problem instead of removing it.
- Follow-ups
-
- Why can the parallel version print a different count on each run?
- Which version would you merge, and why?
- When is a mutable accumulator inside a lambda acceptable?
From the lesson Java lambda expressions and target typing
Model answer, Pro. Model answers open with any pass; prices are in the notice on this page.Opening the model answers with your pass…
Model answers open with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so the model answers can’t be opened at the moment. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
You are converting a large codebase's anonymous Runnable, Comparator and listener classes to lambdas with an IDE quick-fix. Which conversions can change behaviour, and how would you find them before they reach production?
- What it tests
- The differences between anonymous classes and lambdas: the meaning of this, own state, object identity and locking, listener removal, serialization, and how to review a mechanical change safely.
- First step
- List what an anonymous class has that a lambda does not: its own this, fields, toString, equals and identity, and a new object on every evaluation. Then search the converted code for each use: this inside the body, synchronized(this), fields, objects used as map keys or removed from listener lists, and serialized callbacks.
- Follow-ups
-
- Why does synchronized (this) inside the old body need attention?
- A listener added as a lambda can no longer be removed. Why, and what is the fix?
- What would you check before converting a callback that is serialized?
From the lesson Java lambda expressions and target typing
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 4 minutes
-
Mid to senior
A profile of a pricing service shows millions of Integer objects created per second inside a loop that applies a chain of Function<Integer, Integer> discount rules to prices. What would you change, and how would you show that the change helped?
- What it tests
- Boxing in generic functional interfaces, the primitive specialisations and their composition methods, the Integer cache, and measuring allocation instead of guessing.
- First step
- Explain where the objects come from: generics take only reference types, so every argument and result of a Function<Integer, Integer> is an Integer, and Integer.valueOf reuses objects only from -128 to 127. Prices are larger, so each rule in the chain can allocate. IntUnaryOperator keeps the values as int.
- Follow-ups
-
- Does IntUnaryOperator still let you chain the rules?
- How would you measure the allocation before and after?
- When is the boxed version good enough?
From the lesson Functional interfaces in java.util.function
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
A nightly job reads 10,000 files with paths.stream().map(p -> Files.readString(p)), which does not compile. Compare three ways to make it work, and choose one for a job that must report every file it could not read.
- What it tests
- Why Function cannot throw checked exceptions, wrapping in UncheckedIOException, a function type that declares the exception, and returning a result per element so that failures are collected.
- First step
- Name the cause: Function.apply declares no checked exceptions, so its lambda may not throw IOException. Then lay out the options: catch inside the lambda, wrap in UncheckedIOException through a small adapter, or map each path to a result that holds either the text or the error. Only the last reports every failure.
- Follow-ups
-
- Why does wrapping in UncheckedIOException stop at the first unreadable file?
- How would you represent a result that is either text or an error?
- Would you write your own throwing functional interface or use a library?
From the lesson Functional interfaces in java.util.function
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
After a refactoring that replaced () -> config.reload() with config::reload, a service fails at startup with a NullPointerException instead of failing at the first reload. Explain why, and decide which behaviour you want.
- What it tests
- When a bound method reference evaluates its receiver compared with a lambda, failing early at wiring against failing late at the call, and dependencies that are set after construction.
- First step
- Point to the evaluation rule: the expression before :: is evaluated when the reference is created, and a null receiver throws there (JLS 15.13.3). The lambda read config only when it ran. So the refactoring moved the failure from the first reload to the wiring code, where config was still null.
- Follow-ups
-
- config is a field that a framework sets after construction. What changes?
- Does this::reload behave the same way?
- How would a test have caught the change?
From the lesson Java method references: the four kinds
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
Money has static String format(Money m), used across services as prices.stream().map(Money::format). A teammate adds an instance method String format() to Money, and several services stop compiling. Explain why, and how library authors and callers can avoid it.
- What it tests
- How javac resolves ClassName::method with a static and an instance search, why two matches are an error, and API design that keeps method references compiling.
- First step
- For Money::format with a Function<Money, String>, javac searches for a static format(Money) and for an instance format() called on the argument. Before the change only the static method existed; now both match, so the reference is ambiguous, the same error that Integer::toString gives.
- Follow-ups
-
- Do services that were already compiled break as well?
- How would you name the two methods instead?
- What can callers write so that the next such change cannot break them?
From the lesson Java method references: the four kinds
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Fresher to mid
A TreeSet<Order> built with Comparator.comparing(Order::amount) keeps fewer orders than were added, and nothing throws. Explain why, and fix it without changing the order in which the orders are listed.
- What it tests
- That sorted sets and maps use the comparator, not equals, to find duplicates; consistency with equals; and choosing tie-breakers that make the comparator a total order.
- First step
- Explain how a TreeSet finds duplicates: it never calls equals, it treats two elements as the same when its comparator returns 0. Comparing by amount alone returns 0 for every pair of orders with the same amount, so all but the first of them are silently rejected by add, which returns false.
- Follow-ups
-
- Is a HashSet of the same orders affected?
- Which tie-breaker would you add, and why that one?
- How would a test catch this before production?
From the lesson Java comparators and multi-key sorting
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
Design the comparator for a contest leaderboard: more problems solved first, then less penalty time, then the earlier last accepted submission; contestants with no accepted submission have no time at all. Explain your choices and how you would test the comparator.
- What it tests
- Chaining comparators in rule order, reversing one key only, primitive comparators, null keys, a final tie-breaker for a total order, and testing a comparator's contract.
- First step
- Write the keys in the order of the rule and decide each direction: solved count descending, penalty ascending, last accepted time ascending with missing times last, and finally a unique key such as the contestant id, so that the comparator returns 0 only for the same contestant and the ranking never depends on input order.
- Follow-ups
-
- Where exactly does reversed() go, and why?
- Why add a unique final key if the sort is stable anyway?
- The leaderboard has 200,000 rows and updates every few seconds. What changes?
From the lesson Java comparators and multi-key sorting
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Fresher to mid
A method returns cache.find(key).orElse(loadFromDatabase(key)). Metrics show the database is queried on every call, even for keys that are in the cache. Explain why, fix it, and say how a test would prove the fix.
- What it tests
- Eager evaluation of method arguments, the difference between orElse and orElseGet, and testing that a costly default is not computed.
- First step
- Point out that Java evaluates every argument before calling a method, so loadFromDatabase(key) runs on every call and orElse only decides whether to use its result. orElseGet(() -> loadFromDatabase(key)) passes a Supplier, which is called only when the cache misses.
- Follow-ups
-
- loadFromDatabase can throw a checked SQLException. What changes?
- When is orElse still the right choice?
- How does or() differ from orElseGet()?
From the lesson Java Optional done right
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
A code review finds Optional<String> middleName as a field of a class that is serialized into a distributed cache, and service methods that take Optional parameters. What problems do you expect, and what do you recommend instead?
- What it tests
- What Optional was designed for, its missing Serializable, null Optionals, the cost for callers, and better shapes for optional fields and parameters.
- First step
- Start from the design intent: the Optional Javadoc says it is primarily for return values. Then name the concrete failures: Optional does not implement Serializable, so serializing the class throws NotSerializableException at run time; and an Optional parameter can itself be null, so it adds a third state instead of removing one.
- Follow-ups
-
- The class is a record serialized with a JSON library rather than Java serialization. Does the advice change?
- How would you design a method with two optional parameters instead?
- Is Optional<List<String>> ever the right return type?
From the lesson Java Optional done right
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Fresher to mid
After a loop that filled a TreeMap was refactored to Collectors.groupingBy, a monthly report prints its categories in a different order and a downstream file comparison fails. Explain and fix it, and say which test should have caught it.
- What it tests
- What groupingBy promises about its map, map factories and downstream collectors, keeping empty groups, and characterisation tests for refactorings.
- First step
- Read what the collector promises: the Collectors Javadoc gives no guarantee about the type or order of the map that groupingBy(classifier) returns, and the implementation uses a hash map. The loop used a TreeMap, sorted by key, so the fix is groupingBy(classifier, TreeMap::new, downstream).
- Follow-ups
-
- Which map would you choose, and why that one?
- The report must now list categories by total, highest first. What changes?
- How do you keep such refactorings safe across a large codebase?
From the lesson Refactoring Java loops to a functional style
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
After a refactoring, a metrics counter that is incremented inside map() reports far fewer processed records than before, although the pipeline's results are correct. Explain what changed, and how you would restructure the code.
- What it tests
- Laziness and short-circuiting, operations the stream may skip such as count(), why lambdas in a pipeline should be free of side effects, and where counters belong.
- First step
- Ask which terminal operation ends the pipeline. A stream pulls elements one at a time, so findFirst, anyMatch or limit stop it early, and count() may skip the pipeline when the source knows its size. A counter inside map counts the elements the stream chose to process, not the records the old loop visited.
- Follow-ups
-
- Is peek() the right place for the counter?
- What else changes if the stream becomes parallel?
- How would you count both the records seen and the records kept?
From the lesson Refactoring Java loops to a functional style
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes