Special Case — Optimization Drills¶
Category: Control-Flow Patterns — return a dedicated object for a recurring exceptional condition instead of branching for it at every call site.
9 inefficient implementations + optimizations.
Apple M2 Pro, single thread.
Optimization 1: Replace Duplicated Sentinel Branches¶
Slow / unmaintainable¶
String name = (c == null) ? "occupant" : c.name();
Plan plan = (c == null) ? Plan.BASIC : c.plan();
boolean tax = (c == null) ? false : c.isTaxExempt();
Three data-dependent branches per call site, copied across the codebase.
Optimized¶
Customer c = repo.find(id); // UnknownCustomer if absent
String name = c.name(); // "occupant"
Plan plan = c.plan(); // BASIC
boolean tax = c.isTaxExempt();// false
This is primarily a maintainability optimization — but on hot paths where the miss rate is unpredictable, removing the data branch also removes branch mispredictions (see Optimization 2).
Optimization 2: Branch → Dispatch on Unpredictable Misses¶
Slow — unpredictable branch¶
for (String id : ids) { // ~50% miss, random
Customer c = map.get(id);
total += (c == null) ? 0 : c.spend();
}
A 50/50 unpredictable branch costs a ~15-cycle mispredict roughly half the time.
Optimized — polymorphic, no data branch¶
Benchmark (JMH)¶
The virtual call is monomorphic-friendly and has no mispredict. ~2× on this pattern.
Optimization 3: Share the Singleton (Don't Allocate Per Miss)¶
Slow¶
def find(cid):
row = db.get(cid)
return UnknownCustomer() if row is None else Customer(row.name, row.plan) # new each time
Optimized¶
UNKNOWN_CUSTOMER = UnknownCustomer() # once
def find(cid):
row = db.get(cid)
return UNKNOWN_CUSTOMER if row is None else Customer(row.name, row.plan)
Benchmark (Python, per call)¶
A stateless special case is a constant — construct it once.
Optimization 4: Zero-Size Special Case in Go¶
Slow¶
type unknownCustomer struct {
name string // unused, always "occupant"
plan string // unused, always "BASIC"
}
Each value carries dead fields; storing it in an interface may box and escape.
Optimized¶
type unknownCustomer struct{} // zero-size
func (unknownCustomer) Name() string { return "occupant" }
func (unknownCustomer) Plan() string { return "BASIC" }
var Unknown Customer = unknownCustomer{} // shared, no per-use alloc
Zero-size structs cost nothing and a single shared interface value avoids per-call allocation. Confirm with go build -gcflags='-m'.
Optimization 5: Cache Negative Lookups (Avoid Re-Querying)¶
Slow¶
func (r *Repo) Find(id string) Customer {
row := r.db.Query(id) // hits DB even for known-missing IDs, every call
if row == nil { return Unknown }
return realCustomer{row.Name, row.Plan}
}
A hot path repeatedly querying for IDs that don't exist hammers the DB.
Optimized — cache the special case too¶
func (r *Repo) Find(id string) Customer {
if c, ok := r.cache[id]; ok {
return c // may be Unknown — a valid cached value
}
c := r.load(id) // returns Unknown on miss
r.cache[id] = c
return c
}
Tradeoff¶
- Use a short TTL or invalidate on write, or a late-registering customer stays "unknown" (see find-bug Bug 10).
Optimization 6: Devirtualize a Monomorphic Site¶
Context¶
If a call site is monomorphic (one concrete type) or bimorphic (two), HotSpot devirtualizes and inlines the call — dispatch cost vanishes.
Optimization¶
Keep special-case types few per call site. A site that sees RealCustomer + UnknownCustomer stays bimorphic and inlinable. Ten special-case types at one hot site go megamorphic and fall back to vtable lookup (still cheap, but no inlining).
Rule: don't proliferate special-case subtypes on the hottest paths.
Optimization 7: Avoid Re-Wrapping at Every Boundary¶
Slow¶
# Each layer re-checks and re-wraps
def service_find(cid):
c = repo_find(cid)
return UNKNOWN_CUSTOMER if c is None else c # repo already returns special case!
Double-handling: the repo already returns the special case, the service re-decides.
Optimized¶
Decide once, at the lowest layer. Higher layers pass the object through untouched.
Optimization 8: Lazy-Init Parameterized Special Cases¶
Slow¶
public Subscription find(String id) {
Row r = db.query(id);
String reason = computeHoldReason(r); // always computed
if (r == null) return UnknownSubscription.INSTANCE;
if (r.status() == HOLD) return new SuspendedSubscription(reason);
return new ActiveSubscription(r.renewsOn());
}
computeHoldReason runs for every lookup, even active/unknown ones that ignore it.
Optimized¶
if (r == null) return UnknownSubscription.INSTANCE;
if (r.status() == HOLD) return new SuspendedSubscription(computeHoldReason(r)); // only when needed
return new ActiveSubscription(r.renewsOn());
Compute special-case parameters only on the branch that uses them.
Optimization 9: Instrument Without Slowing the Hot Path¶
Slow¶
if (r == null) {
log.info("unknown customer for id=" + id); // string concat + I/O per miss
return UnknownCustomer.INSTANCE;
}
Per-miss string concatenation and synchronous logging dominate on a hot path.
Optimized¶
if (r == null) {
unknownCounter.increment(); // cheap atomic, no allocation
return UnknownCustomer.INSTANCE;
}
A counter gives you the observability of senior/professional without the per-call cost. Alert on the metric; log at a sampled rate if you need detail.
Optimization Tips¶
How to find issues¶
- Profile allocations —
pprof -alloc_objects, async-profiler — a per-call special-case constructor shows up here. - Check escape analysis in Go:
go build -gcflags='-m'. - Inspect inlining in Java:
-XX:+PrintInlining— verify hot dispatch sites devirtualize. - Watch the DB — negative lookups that aren't cached are a common hidden cost.
Optimization checklist¶
- Replace duplicated sentinel branches with one special case.
- Share stateless special cases as singletons.
- Use zero-size structs (Go) / immutable singletons (Java/Python).
- Cache negative lookups (with TTL/invalidation).
- Keep special-case types few per hot call site (stay mono/bimorphic).
- Decide once, at the lowest layer; don't re-wrap.
- Compute special-case parameters lazily.
- Instrument with counters, not per-call logs.
Anti-optimizations¶
- ❌ Allocating a new special case per call. Share the singleton.
- ❌ Caching negative results forever. Late-arriving rows stay "unknown."
- ❌ Proliferating subtypes on a hot path. Megamorphic sites lose inlining.
- ❌ Special-casing failures to "save" an error path. That masks corruption.
Summary¶
Special Case optimizations are mostly about not paying twice: don't allocate a stateless case per call, don't re-decide at every layer, and don't re-query for known-missing rows. On hot paths with unpredictable misses, swapping a data branch for polymorphic dispatch is a genuine speedup — and a singleton, immutable special case is essentially free.
← Find-Bug · Control Flow · Roadmap
Special Case roadmap complete. All 8 files: junior · middle · senior · professional · interview · tasks · find-bug · optimize.
In this topic