What is the difference between orElse and orElseGet, and why can it matter for correctness, not just performance?
answer
- orElse → fallback evaluated eagerly, always
- orElseGet → supplier evaluated lazily, only when empty
- Args in Java are evaluated before the call (eager)
- Expensive/side-effecting default → use orElseGet
- Side effect in orElse runs even when value present (a bug)
basics
~20 sorElse(x) always builds x even when the Optional has a value; orElseGet(supplier) only runs the supplier when the Optional is empty. So orElseGet avoids wasted work, and matters when the default is expensive or has side effects.
solid answer
~50 sBoth return the contained value when present and a fallback when empty. The difference is evaluation timing of the fallback. orElse(other) takes an already-computed value, so its argument is evaluated eagerly every time the line runs — even when the Optional is non-empty and the fallback is thrown away. orElseGet(Supplier) takes a lambda and invokes it lazily, only when the Optional is empty. For a cheap constant the choice is cosmetic, but it becomes a correctness issue when the fallback does real work: a database call, object allocation in a hot loop, or anything with side effects. Writing optional.orElse(expensiveLookup()) calls expensiveLookup() unconditionally, which can be slow or even wrong (e.g., it increments a counter or inserts a row) on the path where the Optional already had a value. The rule: orElse for plain constants or already-available values, orElseGet whenever computing the default is costly or has side effects.
code
java · 12 linesOptional<String> present = Optional.of("value");
// orElse: makeDefault() ALWAYS runs, result discarded here
String a = present.orElse(makeDefault());
// orElseGet: supplier NOT called because value is present
String b = present.orElseGet(() -> makeDefault());
static String makeDefault() {
System.out.println("expensive/side-effecting work"); // runs for orElse, not orElseGet
return "default";
}go deeper
Know that orElse provides a default value and orElseGet provides a default via a lambda.
Explain that orElse evaluates its argument eagerly while orElseGet runs the supplier only when empty, and prefer orElseGet for expensive defaults.
Articulate that eager evaluation of orElse makes side-effecting defaults a correctness bug (not just slow), and choose between orElse/orElseGet by cost and side effects.
Generalize the eager-vs-lazy argument-evaluation principle, set conventions/static-analysis to catch orElse with method-call arguments, and reason about default-computation cost across hot paths and APIs.
## The two methods Both `orElse` and `orElseGet` answer the same question — "give me the value, or a fallback if empty" — but they take the fallback in different forms: - **`T orElse(T other)`** — you pass an actual value. - **`T orElseGet(Supplier<? extends T> supplier)`** — you pass a `Supplier`, a no-argument lambda that *produces* the value when called. ## The key difference: when the fallback is evaluated In Java, **method arguments are evaluated before the method runs** (eager evaluation). So with `orElse`, whatever expression you write as the argument is computed *first*, regardless of whether the Optional is empty: ```java Optional<String> opt = Optional.of("present"); String r = opt.orElse(makeDefault()); // makeDefault() RUNS, even though "present" wins ``` Here `makeDefault()` executes and its result is then discarded, because the Optional already had a value. The work was wasted. With `orElseGet`, you hand over a lambda that is **only invoked when the Optional is empty**: ```java String r = opt.orElseGet(() -> makeDefault()); // makeDefault() does NOT run; value is "present" ``` The supplier is a deferred computation; `orElseGet` calls it lazily, on the empty branch only. ## Why this is more than a micro-optimization If the fallback were a pure, cheap constant like `""` or `0`, the difference is just a negligible bit of wasted computation, and `orElse` reads more simply. But two situations turn it into a real defect: 1. **Expensive defaults.** `orElse(repository.findDefault())` hits the database on *every* call, including the (possibly common) case where the Optional is present. In a loop or hot path this is a serious performance bug. `orElseGet(() -> repository.findDefault())` only queries when needed. 2. **Side-effecting defaults.** If the fallback does something observable — logs, increments a metric, creates and registers an object, mutates shared state — then `orElse` performs that side effect *unconditionally*, even when it shouldn't. For example, `orElse(createNewSession())` would create a session every time, leaking objects or double-counting, whereas `orElseGet(() -> createNewSession())` creates one only when truly absent. This is a correctness bug, not just slowness. ## A subtle case: returning null `orElse(null)` is legal and sometimes used to unwrap back to a nullable value at an API boundary. `orElseGet(() -> null)` would do the same lazily. Generally, if you find yourself writing `orElse(null)`, reconsider whether Optional is buying you anything at that point. ## Relationship to other unwrap methods `orElse`/`orElseGet` substitute a value; `orElseThrow` instead raises an exception when empty. They're siblings — choose `orElseGet` over `orElse` when the default is costly, `orElseThrow` when absence is an error rather than a recoverable default. ## Summary - `orElse(x)`: x is evaluated **eagerly, always**. - `orElseGet(() -> x)`: the supplier runs **lazily, only when empty**. - Prefer `orElseGet` whenever the default is expensive or has side effects — there it's a correctness concern, not merely performance. Use `orElse` for plain constants.
- If the default is a simple string constant like \"\", does it matter whether you use orElse or orElseGet?Functionally no — both return the same result. orElse is preferable there for simplicity since evaluating a constant is free and has no side effects. The distinction only matters when the default is expensive or side-effecting.
- Give a concrete example where orElse causes a bug, not just slowness.optional.orElse(createAndRegisterSession()) creates and registers a session on every call, including when the Optional already holds one, leaking sessions or corrupting state. orElseGet(() -> createAndRegisterSession()) only does so when empty.
orElse is like cooking a backup meal every night just in case the takeout fails — even when it arrives. orElseGet is keeping the recipe on hand and only cooking if the takeout actually doesn't show up.
saying these in an interview costs you the question
- Saying orElseGet's supplier always runs (it runs only when empty)
- Claiming the difference is purely performance and never correctness
- Thinking orElse evaluates its argument lazily
- Using orElse(expensiveOrSideEffectingCall()) in hot paths