Compare
Symptom
Human

Code is littered with `if (x !== null) x.do()` checks around the same collaborator. Each new caller adds another check; missing one ships a NullPointerException to production; the absence-handling logic duplicates across callers.

Agent

Null-check branches the agent must trace at every collaborator-using call site. The agent cannot statically verify all check sites stay consistent; new callers risk omitting the check and shipping NullPointerExceptions.

Goal
Human

A Null Object implementing the same interface stands in for the missing collaborator. Callers invoke methods unconditionally; the Null Object returns sensible defaults (return the input unchanged, return zero, do nothing).

Agent

One Null Object class the agent verifies once; all call sites unconditionally invoke the collaborator interface. The agent's edit budget on a collaborator-using method drops because there's no per-call branch to reason about.

Before the refactoring

// Every consumer of Customer.discount and Customer.loyaltyProgram must check for null first.
class Order {
constructor(customer) {
this.customer = customer;
}
finalPrice() {
let price = this.subtotal();
if (this.customer.discount !== null) {
price = this.customer.discount.applyTo(price);
}
if (this.customer.loyaltyProgram !== null) {
price -= this.customer.loyaltyProgram.pointsValue();
}
return price;
}
subtotal() { /* ... */ }
}

After the refactoring

// Null Objects implement the same interface with safe defaults; callers stop branching.
class NoDiscount {
applyTo(price) {
return price;
}
}
class NoLoyaltyProgram {
pointsValue() {
return 0;
}
}
class Order {
constructor(customer) {
// customer.discount and customer.loyaltyProgram are never null;
// they are NoDiscount/NoLoyaltyProgram when absent.
this.customer = customer;
}
finalPrice() {
let price = this.subtotal();
price = this.customer.discount.applyTo(price);
price -= this.customer.loyaltyProgram.pointsValue();
return price;
}
subtotal() { /* ... */ }
}
Example source: Illustrative example written for this site, faithful to Kerievsky's pattern shape in Refactoring to Patterns (Addison-Wesley, 2004), chapter 9. The book uses a Customer hierarchy with a NullCustomer; this JavaScript version uses NoDiscount and NoLoyaltyProgram — same pattern, scaled to two collaborators to show how callers stop branching.
Pressure
Human

Null checks duplicate at every call site; the rules for what 'absent' means duplicate too. Adding a new caller is one more place to remember the check; forgetting one is a runtime bug.

Agent

N call sites × M methods × the null check = the agent's per-edit verification cost. Static analysis catches some missing checks but not all (especially after refactors that change which fields can be null).

Tradeoff
Human

Null Objects hide absence; downstream code may mistake the silent no-op for a real operation, especially when the Null Object's behaviour drifts from the originally-intended default. Debugging 'why did nothing happen?' requires recognizing that a Null Object is in hand.

Agent

A Null Object collapses two distinct runtime states (absent / present) into one type at the call site. The agent loses the ability to statically distinguish a real collaborator from its null stand-in; debugging requires reading the constructor / factory to know which is which.

Relief
Human

Call sites simplify to plain method calls; absence-handling lives in one place (the Null Object); the rules for what 'no discount' or 'no loyalty program' means are visible and testable as a single class.

Agent

Branch count at call sites drops to zero; the agent reads the Null Object's body once to know what 'absent' does, and trusts the type system thereafter. Adding a new caller is one method call, not one method-call-plus-branch.

Trap
Human

A Null Object with subtly-wrong defaults silently corrupts results without throwing. The pattern pays when 'absent' has a well-defined neutral semantics; for collaborators where absence should actually halt the operation, Null Object hides the error the caller expected to see.

Agent

A Null Object that quietly returns the wrong neutral element (zero where one was needed, identity where blocking was needed) produces silent failures the agent cannot detect from static reading. The pattern requires careful semantic alignment between 'absent' and the chosen default.