Optional.orElse vs orElseGet — Eager vs Lazy Defaults
Fast recall
Quick revision
Core answer
Supplier and only invokes it if the Optional is empty (lazily). For cheap defaults like constants (orElse("Unknown"), orElse(0)), both are fine. But for expensive defaults — a database lookup, a network call, an object construction — orElse always pays the cost even when the value is present, which can devastate performance under load. The rule: use orElse for constants and pre-computed values; use orElseGet for anything that involves I/O, allocation, computation, or side effects. The most common bug is optional.orElse(repo.findGuestUser()) — the database is called every time, even when the Optional is non-empty. The fix is optional.orElseGet(() -> repo.findGuestUser()). The principle generalizes: any Java method that takes a value as an argument vs a Supplier should be chosen on the same eager-vs-lazy basis.Whether the candidate understands eager vs lazy evaluation, can identify the perf bug of `orElse` with expensive defaults, and knows when each is appropriate
Using `orElse(expensiveCall())` thinking it only runs when empty. Confusing `orElse` and `orElseGet` semantically (both 'return a default')
Explain that this generalizes to other lazy/eager APIs (`Map.computeIfAbsent`, `Logger.info` with Supplier overload), discuss measurable perf impact under load, and mention `orElseThrow(Supplier)` follows the same lazy pattern
Interview-ready guide
Interview answer
Both orElse and orElseGet return a default when the Optional is empty, but they differ in when the default is computed.
`orElse(value)`
- Takes a value directly
- Java evaluates the argument before the method call — the default is always computed
- Even when the Optional is present, the default expression runs
- Use for constants, singletons, literals
`orElseGet(supplier)`
- Takes a
Supplier - The supplier is only invoked when the Optional is empty
- If present, the supplier is never called
- Use for expensive defaults: method calls, I/O, allocation, side effects
Why the difference matters
Java is strict-evaluated: when you write foo(bar()), bar() always runs first, even if foo ignores its argument. So optional.orElse(slowFetch()) runs slowFetch() on every call — even when the Optional is present.
With optional.orElseGet(() -> slowFetch()), the lambda is deferred; Optional only calls it on the empty branch.
The classic bug — caching defeated
// WRONG: DB called every time, even on cache hit
User u = userCache.find(email).orElse(loadFromDatabase(email));
// RIGHT: DB called only on cache miss
User u = userCache.find(email).orElseGet(() -> loadFromDatabase(email));Under load, the wrong version makes the cache pointless — you pay the slow path on every request.
When each is appropriate
`orElse` for:
- Constants:
orElse("Unknown"),orElse(0) - Singletons:
orElse(EMPTY_LIST),orElse(GUEST_USER) - Pre-computed values already in memory
`orElseGet` for:
- DB or network calls:
orElseGet(() -> repo.findGuest()) - Env lookups:
orElseGet(() -> System.getenv("DEFAULT")) - Allocations:
orElseGet(() -> new User()) - Anything with side effects: logging, metrics
Same pattern for `orElseThrow`
- Argless
orElseThrow()(Java 10+) throwsNoSuchElementException— no Supplier orElseThrow(() -> new UserNotFoundException(email))lazily constructs the exception- Important when exception construction is expensive (stack traces, message formatting)
The pattern generalizes — lazy vs eager APIs
Map.computeIfAbsent(key, Function)— lazy- SLF4J:
logger.info("x={}", supplier)— lazy formatting (only formats if level enabled) Stream.generate(Supplier)— lazy production- Anywhere you see overloads taking
TvsSupplier, the Supplier is for expensive computations
Pitfalls
- "It just builds an object" — small per-call cost adds up under load
- Side effect in `orElse` arg — logs/metrics fire even when value is present (silent bug)
- Reflexively using `orElseGet` —
orElseGet(() -> "Unknown")is uglier thanorElse("Unknown") - Reflexively using `orElse` — perf cost is real for non-trivial defaults
Recommendation: Use `orElse` for constants and singletons — it reads cleaner and there's no cost. Use `orElseGet` for any default that involves a method call, I/O, allocation, or side effect — the lazy supplier saves you from doing the work on the present branch. The classic perf bug is optional.orElse(repo.findGuest()) — DB called every time, defeating any caching. Same logic applies to `orElseThrow(Supplier)` for expensive exception construction. The principle generalizes: anywhere you see two overloads (T vs Supplier), the Supplier version is for cases where the computation isn't free — logging, default loading, fallback computation. Pick based on the actual cost of computing the default, not on which method name is shorter.
`Optional.orElse` vs `orElseGet` — Eager vs Lazy Defaults
Direct Answer
Both return a value when the Optional is empty, but they differ in when the default is computed: orElse(value) takes a value directly and always evaluates that argument (eagerly), even when the Optional is present — because Java evaluates method arguments before the call. orElseGet(supplier) takes a Supplier and only invokes it if the Optional is empty (lazily). For cheap defaults like constants (orElse("Unknown"), orElse(0)), both are fine. But for expensive defaults — a database lookup, a network call, an object construction — orElse always pays the cost even when the value is present, which can devastate performance under load. The rule: use orElse for constants and pre-computed values; use orElseGet for anything that involves I/O, allocation, computation, or side effects. The most common bug is optional.orElse(repo.findGuestUser()) — the database is called every time, even when the Optional is non-empty. The fix is optional.orElseGet(() -> repo.findGuestUser()). The principle generalizes: any Java method that takes a value as an argument vs a Supplier should be chosen on the same eager-vs-lazy basis.
Common Mistakes & Pitfalls
Using orElse(expensiveCall()) thinking it only runs when empty. Confusing orElse and orElseGet semantically (both 'return a default')
Check your understanding
Answer these without looking back at the article.
- Why does `Optional.orElse(slowMethod())` defeat caching?
- Is there ever a case where `orElseGet` is slower than `orElse`?
- What's the difference between `orElseThrow()` and `orElseThrow(Supplier)`?
- Why does SLF4J's `logger.info("x={}", supplier)` overload exist?
- How would you measure the actual perf difference between `orElse` and `orElseGet` for a given default?
Related interview prep categories
1Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.