Skip to main content
CleanCodeMastery

Refused Bequest: The Child Who Refused the Sweet Shop Recipes

Learn the Refused Bequest code smell with a family sweet shop story, Liskov violations in TypeScript and C#, and the delegation cure explained step by step.

24 min read Updated June 11, 2026beginner
code smellsoo abusersrefused bequestinheritanceliskov substitutiondelegationtypescript

🍬 The sweet shop and the unwilling heir

In Indore there is a famous sixty-year-old sweet shop: Gupta Mithai Bhandar. Grandfather Gupta built it from a single kadhai on a handcart. His son, Suresh ji, grew it into the shop every wedding planner in the city knows. And the whole family knows the plan — Suresh ji's eldest son, Rohan, will take over one day. With the shop comes the bequest: the secret recipe book (240 recipes!), the giant brass kadhai, the festival-season customer list, and the early-morning milk supplier contacts.

There is one problem. Rohan does not want to make sweets. Rohan wants to run a gym.

But family is family, so Rohan "inherits" the shop. And what happens?

  • A customer asks for kaju katli. Rohan says, "Sorry, we don't make that anymore." (He says this for 230 of the 240 recipes.)
  • The milk supplier, Sharma ji, calls at 5 a.m. as he has for thirty years. Rohan has blocked the number.
  • The brass kadhai? He uses it as a plant pot near the treadmill.
  • The only things he actually uses from the bequest: the building, the cash counter, and grandfather's good name on the signboard.

Now think about the customers. The signboard still says Gupta Mithai Bhandar. People walk in expecting a sweet shop — that is what the name promises — and they get protein shakes. Old Mishra ji, a customer for forty years, walks in three days before Diwali with a list for two hundred laddoo boxes and walks out shaking with anger. Not because a gym is bad — his own grandson goes to a gym — but because the promise on the board does not match what is inside. Some regulars have even learned a sad trick: before entering, they peep through the window to check whether it is "really still a sweet shop" — checking the actual type because the declared type cannot be trusted.

This is exactly the Refused Bequest code smell. A subclass signs up as "a kind of" its parent, inherits everything, and then refuses most of it — methods that throw "not supported," fields never used, promises never kept. And callers, like Mishra ji, start writing instanceof checks: peeping through the window because the signboard lies. Keep Rohan, Suresh ji, Mishra ji, and that Diwali order in mind — this whole post is their story.

🔍 What is this smell?

Refused Bequest is one of the Object-Orientation Abuser smells, and it abuses OOP's most powerful — and most dangerous — feature: inheritance.

When you write class Child extends Parent, you are making a very strong public statement: "a Child IS a Parent. Anywhere a Parent works, a Child works too." The parent's public methods are a contract, and by inheriting, the child signs that contract. Every caller in the entire codebase is allowed to rely on it.

The smell appears when the child signs the contract but refuses to honour it:

  • Inherited methods are overridden to throw (NotSupportedException, UnsupportedOperationException) or to silently do nothing meaningful.
  • Inherited fields sit unused or are forced into nonsense values.
  • The child overrides nearly everything, keeping only the extends keyword while sharing almost no real behaviour.
  • Code that works perfectly with the parent breaks when handed this child.

Why does this happen? Almost always because inheritance was used for code reuse instead of for a true "is-a" relationship. The parent had one or two handy methods. Someone subclassed it just to grab them — like inheriting a whole sweet shop because you wanted the building. The other 238 recipes came along uninvited, and now they must be refused, one embarrassing "sorry, we don't do that" at a time.

💡

One-line summary: Refused Bequest is a subclass that keeps the inheritance link but rejects most of what it inherits — the type hierarchy tells a lie, and every caller who believes the lie gets hurt.

There is a formal principle behind the pain: the Liskov Substitution Principle (LSP) — the "L" in SOLID, from computer scientist Barbara Liskov. In simple words: a subclass object must be usable anywhere its parent is expected, without surprises. A child that throws on inherited methods is the textbook LSP violation. Refused Bequest is the smell you can see; the LSP break is the wound underneath.

College corner: LSP is sharper than "the child should not throw." Barbara Liskov's formulation is about behavioural subtyping, and it gives you a precise checklist. A subclass override may not strengthen preconditions (demand more than the parent demanded — for example, the parent's orderMilk(litres) accepts any positive number, so the child cannot insist on multiples of ten). It may not weaken postconditions (deliver less than the parent promised — the parent's makeSweet promises a sweet, so returning an apology string breaks it). It must preserve the parent's invariants, and it must not surprise callers with new exception types on old methods. Notice that the compiler checks signatures, not behaviour — your override can compile perfectly and still violate every rule above. That is why LSP violations are caught by tests and code review, not by the type checker. A practical college-level trick: run the parent's entire unit-test suite against each subclass. Any red test is a refused bequest with a stack trace attached.

One important distinction before we go on. There are two levels of refusal:

  1. Refusing implementation — the child inherits the contract fully but does some inherited things its own (still valid) way. Often acceptable.
  2. Refusing the interface — the child rejects the public contract itself, throwing or misbehaving on methods callers may use. This is the damaging form, because it breaks substitution.

The whole smell on one map:

Figure 1: Refused Bequest at a glance — causes, signs, damage, and cures

🕵️ How to spot it

The checklist:

  • A subclass overrides methods to throw "not supported" or to return dummy values like null or -1.
  • Inherited public methods are never called on the subclass anywhere in the codebase.
  • Inherited fields stay at their defaults forever in this subclass.
  • Callers write if (x instanceof ChildType) before calling certain methods — they have learned not to trust the hierarchy.
  • The subclass overrides almost every parent method; little real behaviour is shared.
  • Unit tests written against the parent type fail (or are skipped!) for this subclass.
  • The class comment or commit history says things like "extends X just to reuse the connection logic."

Symptom table:

Symptom you seeWhat it really means
throw new NotSupportedException() in an overrideThe child publicly refuses a contract it publicly signed
Inherited methods never called on the childThe bequest is dead weight widening the class surface
instanceof checks before method callsCallers no longer trust the type; polymorphism is already dead here
Child overrides ~everythingThe extends is for one or two goodies, not a real is-a
Parent's tests fail on the childDirect, measurable Liskov violation
"Is-a" sentence sounds wrong out loud"A gym IS A sweet shop"? If the sentence is absurd, so is the hierarchy

That last row is the easiest classroom test: say the inheritance out loud as an English sentence. "A ReadOnlyList is a MutableList" — absurd, a read-only thing is not a mutable thing. "A Penguin is a FlyingBird" — absurd. If the sentence makes you wince, the code will too.

There is also a quantitative version of the test. Audit the bequest: of everything the parent hands down, how much does the child actually honour? Here is Rohan's audit:

Figure 2: Rohan's inheritance audit — how much of the bequest the gym actually uses

When ninety percent of the inheritance is refused, blocked, or repurposed as a plant pot, the extends keyword is carrying a ten percent truth and a ninety percent lie. Compare that with a healthy subclass — a KajuKatliCounter extends SweetShop that adds one speciality — where the pie would be almost entirely "used as inherited."

⚠️ Why it is a problem

Cost 1: Substitution becomes a minefield. The entire point of a subclass is that any code expecting the parent can receive the child. With a refused bequest, some calls work and some explode at runtime. Every polymorphic call site is now a possible landmine, and nothing in the type system warns you which ones.

Cost 2: The hierarchy misleads every reader. Inheritance is the loudest design statement a codebase makes. New developers reason from it: "this is a MutableList, so I can add to it." When the statement is false, people confidently write bugs.

Cost 3: Type checks creep back in. To survive the landmines, callers add instanceof checks — and now you have re-created the Switch Statements smell. Knowledge about subclass quirks scatters across the codebase, the very thing polymorphism was supposed to prevent.

Cost 4: Dead surface area. Unused inherited members make the child look more capable than it is. Auto-complete offers methods that throw. Documentation generators list features that do not exist. Every reader pays a small confusion tax.

Figure 3: How a convenience inheritance rots into scattered type checks

See the loop at the bottom — the crash-and-guard cycle never ends, because the root lie (extends) is still standing.

Now watch the Diwali disaster as a message sequence. The festival ordering system was written years ago, honestly, against SweetShop. It has no idea a gym is hiding in its shop list:

Figure 4: The Diwali order system trusts the signboard — and the gym throws

The crash lands inside the order system's stack trace, not the gym's. That is the cruel signature of this smell: the liar walks away, and the honest caller gets blamed. Here is what that day feels like for the developer on support duty:

Figure 5: A developer's festival week with a refused bequest in production

Notice the middle section: the quick instanceof patch feels like progress and scores low anyway, because every guard is a new place that must change when the next fake sweet shop appears. Only the last section — fixing the hierarchy itself — brings the mood (and the design) back up.

💻 A real-life code example

Let us code the sweet shop. The original family business:

// The parent: a full-fledged sweet shop
class SweetShop {
  protected recipes = new Map<string, string>(); // 240 family recipes
  protected milkSupplier = "Sharma Dairy, 5 a.m. daily";
 
  constructor(public readonly signboard: string) {
    this.recipes.set("kaju katli", "cashew, sugar, ghee...");
    this.recipes.set("motichoor laddoo", "besan, saffron, ghee...");
    // ...238 more
  }
 
  makeSweet(name: string): string {
    const recipe = this.recipes.get(name);
    if (!recipe) throw new Error(`No recipe for ${name}`);
    return `Fresh ${name} made using: ${recipe}`;
  }
 
  orderMilk(litres: number): string {
    return `Ordered ${litres}L from ${this.milkSupplier}`;
  }
 
  sellAtCounter(item: string, price: number): string {
    return `Sold ${item} for ₹${price}`;
  }
}

Now Rohan "inherits" — because he wants the building, the counter, and the signboard goodwill:

// BAD CODE: a gym pretending to be a sweet shop
class GuptaGym extends SweetShop {
  constructor() {
    super("Gupta Mithai Bhandar"); // keeps grandfather's signboard!
  }
 
  // Refused bequest #1: 240 recipes, all refused
  override makeSweet(name: string): never {
    throw new Error("Sorry, we don't make sweets anymore.");
  }
 
  // Refused bequest #2: supplier contact, refused
  override orderMilk(litres: number): never {
    throw new Error("We only stock protein shakes.");
  }
 
  // The ONLY inherited thing actually used:
  // sellAtCounter() — for gym memberships.
 
  startWorkout(member: string): string {
    return `${member} started training!`;
  }
}

And here is the part that hurts — innocent code, written correctly against the parent, explodes when handed the child:

// This function is 100% correct for any real SweetShop...
function prepareDiwaliOrder(shop: SweetShop): string[] {
  shop.orderMilk(50);                      // BOOM with GuptaGym
  return [
    shop.makeSweet("kaju katli"),          // BOOM with GuptaGym
    shop.makeSweet("motichoor laddoo"),
  ];
}
 
const shops: SweetShop[] = [new SweetShop("Agarwal Sweets"), new GuptaGym()];
// Somebody, somewhere, will eventually write:
shops.forEach(prepareDiwaliOrder);          // runtime crash, festival ruined

Notice who gets blamed when this crashes. Not GuptaGym, whose lie caused it — but poor prepareDiwaliOrder, where the stack trace points. Refused bequests make innocent code look guilty. And the usual "fix" makes everything worse:

// The smell spreading: callers peep through the window
function prepareDiwaliOrderDefensive(shop: SweetShop): string[] {
  if (shop instanceof GuptaGym) return [];  // special-casing the liar
  shop.orderMilk(50);
  return [shop.makeSweet("kaju katli")];
}

Every such guard is one more place that must change when the next fake sweet shop appears. Measure the two designs side by side:

Figure 6: The gym honours 1 of 3 inherited promises; after the refactor, every class honours all of its promises

The first bar is the smell in one number: a class whose public surface is mostly landmines. The other two bars are the goal — smaller, maybe, but every method real.

🧹 Cleaning it up, step by step

Step 1: Ask the is-a question honestly. "A gym IS a sweet shop"? No. What Rohan actually wants is part of the shop: the counter (billing) and the premises. The fix is to stop inheriting the whole and start holding the parts — this refactoring is called Replace Inheritance with Delegation.

Step 2: Extract the genuinely shared part. What do a sweet shop and a gym truly have in common? A retail counter. Pull only that into its own small class:

// Step 2: the real shared thing, extracted
class SalesCounter {
  private total = 0;
 
  sell(item: string, price: number): string {
    this.total += price;
    return `Sold ${item} for ₹${price}`;
  }
 
  dailyTotal(): number {
    return this.total;
  }
}

Step 3: Both businesses have a counter; neither is the other. Inheritance becomes composition:

// Step 3: is-a becomes has-a — nothing left to refuse
class SweetShop {
  private counter = new SalesCounter();      // has-a
  private recipes = new Map<string, string>();
 
  makeSweet(name: string): string { /* ...as before... */ return ""; }
  orderMilk(litres: number): string { /* ...as before... */ return ""; }
  sellAtCounter(item: string, price: number): string {
    return this.counter.sell(item, price);   // delegate
  }
}
 
class Gym {
  private counter = new SalesCounter();      // has-a, same helper!
 
  sellMembership(plan: string, price: number): string {
    return this.counter.sell(`${plan} membership`, price);
  }
 
  startWorkout(member: string): string {
    return `${member} started training!`;
  }
  // No makeSweet. No orderMilk. Nothing to throw. Nothing to refuse.
}

Step 4: If substitution is still needed, share a contract both can honour. Suppose the city's "Shops Association" software needs to handle all businesses uniformly. Give it an interface containing only the promises everyone can keep:

// Step 4: a truthful common contract
interface Business {
  dailySales(): number;
}
 
class SweetShop implements Business {
  dailySales() { return this.counter.dailyTotal(); }
  // ...
}
 
class Gym implements Business {
  dailySales() { return this.counter.dailyTotal(); }
  // ...
}
 
// Works for EVERY business — no instanceof, no landmines
function collectAssociationFee(b: Business): number {
  return b.dailySales() * 0.001;
}

Here is the finished design as a class diagram — note how the arrow directions changed from "extends" to "has" and "implements":

Figure 7: The cured design — two honest classes share a helper and a truthful contract
Figure 8: Before — a lying is-a; After — honest has-a plus a small truthful contract

Where the parent is partly right — several children share some behaviour but each refuses different bits — two sibling refactorings help instead: Extract Superclass (lift only the truly common members into a new, smaller parent) and Push Down Method / Push Down Field (shove members that only one child uses down into that child). The rule of thumb: make the parent so small that no child refuses anything.

A hierarchy, like the shop, moves through recognisable health states over its life. The skill is noticing which state yours is in before the festival crash:

Figure 9: The health states of an inheritance hierarchy — and the two roads out of refusal

The left road (slim the parent) suits hierarchies that are mostly right. The right road (delegation) suits hierarchies, like Rohan's, that were never true in the first place.

⚠️

The famous classroom example is real: java.util.Stack extends Vector, so every Java Stack inherits insertElementAt and friends — methods that let you break the last-in-first-out rule by poking the middle of the stack. The Java team's own documentation now steers people to Deque instead. Even standard libraries get this wrong; check your is-a sentences twice.

The same smell in C# 🐧

A shorter C# example — the classic bird hierarchy:

// BAD: not every bird honours the flying contract
class Bird
{
    public virtual string Fly() => "Flying high!";
    public virtual string Eat() => "Pecking at seeds.";
}
 
class Penguin : Bird
{
    public override string Fly()
        => throw new NotSupportedException("Penguins cannot fly!");
    // Eat() is fine — but the contract is already broken.
}
 
// Innocent code, runtime explosion:
void Migrate(List<Bird> flock)
{
    foreach (var bird in flock) Console.WriteLine(bird.Fly()); // BOOM at the penguin
}

The fix: shrink the parent's promise to what all birds can keep, and move flying into a separate, optional contract:

// GOOD: small truthful base + optional capability interface
abstract class Bird
{
    public virtual string Eat() => "Pecking at seeds.";
}
 
interface IFlyingBird
{
    string Fly();
}
 
class Sparrow : Bird, IFlyingBird
{
    public string Fly() => "Flying high!";
}
 
class Penguin : Bird
{
    public string Swim() => "Torpedo mode!";   // its own real talent
}
 
// Only things that CAN fly are asked to fly — checked at compile time
void Migrate(IEnumerable<IFlyingBird> flyers)
{
    foreach (var f in flyers) Console.WriteLine(f.Fly());
}

Nothing throws, nothing refuses, and the compiler — not a runtime exception — stops you from asking a penguin to fly.

College corner: this bird fix demonstrates interface segregation (the "I" in SOLID) working hand in hand with LSP. The original Bird bundled two capabilities — eating and flying — into one contract, forcing every subclass to sign for both. Splitting the optional capability into IFlyingBird means each class signs only the promises it can keep, and substitution becomes provable again. There is a general lesson for hierarchy design: a base type should contain the intersection of what all subtypes can do, not the union of what some subtypes can do. Unions belong in small capability interfaces that subtypes opt into. When you find yourself about to write a throwing override, you have just discovered that something in the parent belongs to the union, not the intersection — and that is your cue to extract an interface rather than throw.

🗺️ Where this smell hides in real projects

  • Standard libraries. Java's Stack extends Vector (index methods break LIFO) and the many java.util collections whose remove() throws UnsupportedOperationException from read-only wrappers — both are forms of refused bequest baked into a platform.
  • "Fat" framework base classes. Teams extend a heavyweight BaseController or BaseService full of hooks, then override half the hooks to do nothing. Each empty override is a small refusal.
  • Test doubles. A FakePaymentGateway extends RealPaymentGateway that overrides everything to avoid network calls — it shares zero behaviour and inherits only trouble. It wanted an interface, not a parent.
  • The Square/Rectangle trap. Square extends Rectangle looks mathematically obvious until setWidth must also change height, and code that resizes rectangles breaks on squares. Domain drift creates refusals where none existed on day one — just as the sweet shop was a true is-a for two generations, until Rohan.
  • Read-only views extending mutable models. ReadOnlyOrder extends Order with throwing setters — the refusal is the entire design.
  • Copy-reuse inheritance. "I extended CsvImporter to get its file-reading code, but my class imports XML." One useful private method, twenty refused public ones.

The shared root cause in every case: inheritance chosen for convenience of reuse, not because the is-a sentence is true.

⚖️ When it is okay to ignore

SituationVerdictWhy
Child refuses an implementation detail but honours the full public contractFineSubstitution still works; that flexibility is what overriding is for
Override that does nothing, where "nothing" is genuinely correct for this childFineA SilentLogger.log() that ignores messages keeps the contract: callers expect no return, get no surprise
Framework forces you to extend a heavyweight base (Activity, ControllerBase)TolerateFighting the framework costs more than a few unused inherited members
One small unused inherited helper methodLeave itA crumb of dead weight is not worth a hierarchy surgery
Child throws on inherited public methodsRefactorThis is the damaging form — substitution is broken
Callers already writing instanceof guards around the childRefactor urgentlyThe smell has metastasized into type-switching across the codebase
The is-a sentence sounds absurd out loudRefactorThe hierarchy lies; every future reader will be misled

Two questions place any suspicious subclass on the map: how much of the contract does it refuse, and how widely is it passed around as its parent type?

Figure 10: Judge a subclass by how much it refuses and how widely it travels as the parent type

GuptaGym sits deep in the danger corner: it refuses almost everything and it travels through the codebase inside SweetShop[] lists. A class that refuses a lot but is never substituted (bottom-right) can wait; a refuser that travels cannot.

ℹ️

Quick honesty meter: refusal of implementation = usually fine; refusal of interface (the public promises) = real smell. Ask: "could code written for the parent crash or misbehave with this child?" If yes, fix it.

🛠️ Which refactorings cure it

RefactoringUse it when
Replace Inheritance with DelegationThe main cure: the child wants only part of the parent — hold it as a field, expose only what you honour
Extract SuperclassSeveral classes share some behaviour — lift only the common part into a new small parent nobody refuses
Extract InterfaceThe shared thing is a contract, not code — define the truthful promise set and implement it
Push Down MethodA parent method matters to only some children — move it into those children
Push Down FieldSame for fields only certain children use
Rename / re-document the hierarchySometimes the code is right and the names lie — make the is-a sentence true by renaming

📦 Quick revision box

+----------------------------------------------------------------+
|  REFUSED BEQUEST — CHEAT SHEET                                 |
+----------------------------------------------------------------+
|  Story    : Rohan inherits the sweet shop, refuses 230 of 240  |
|             recipes, runs a gym under grandfather's signboard. |
|  Smell    : Subclass keeps `extends` but rejects the bequest:  |
|             throwing overrides, unused members, broken promises|
|  Spot it  : NotSupportedException overrides, instanceof guards,|
|             parent tests fail on child, absurd is-a sentence.  |
|  Danger   : Liskov violation -> innocent caller code crashes;  |
|             type checks spread; hierarchy misleads everyone.   |
|  Cure     : Replace Inheritance with Delegation (is-a -> has-a)|
|             Extract Superclass / Interface; Push Down members. |
|  OK when  : Refusing implementation (not interface); framework |
|             bases; one harmless unused helper.                 |
|  Mantra   : Say the is-a sentence out loud. If you wince, fix. |
+----------------------------------------------------------------+

✍️ Practice exercise

A school management system contains this hierarchy. It compiles, it "works," and it is lying:

class Teacher {
  constructor(public name: string) {}
  teachClass(subject: string): string { return `${this.name} teaches ${subject}`; }
  gradeExams(count: number): string  { return `${this.name} graded ${count} papers`; }
  attendStaffMeeting(): string       { return `${this.name} attended the meeting`; }
  collectSalary(): number            { return 45000; }
}
 
// A guest speaker visits once a year for Career Day...
class GuestSpeaker extends Teacher {
  override gradeExams(count: number): never {
    throw new Error("Guests do not grade exams");
  }
  override attendStaffMeeting(): never {
    throw new Error("Guests do not attend staff meetings");
  }
  override collectSalary(): number {
    return 0; // guests are unpaid... is this honest or a refusal?
  }
}

Your tasks:

  1. Diagnose: Say the is-a sentence out loud. List every refused member, and classify each refusal: interface refusal (throws / breaks callers) or implementation refusal (different but valid behaviour). Is collectSalary() → 0 honest, or does payroll code expecting a Teacher now mis-handle guests?
  2. Find the victim: Write a function endOfTerm(teachers: Teacher[]) that calls gradeExams on everyone, and show how a GuestSpeaker in the list crashes it — even though endOfTerm is bug-free. (This is your Diwali order in miniature.)
  3. Audit like Figure 2: Draw the pie for GuestSpeaker — of the four inherited methods, how many are honoured, dummy, or throwing? What percentage of the bequest is refused?
  4. Refactor: Apply the cure. Extract a small truthful contract (maybe interface Educator { teachClass(subject: string): string }), turn GuestSpeaker into a standalone class implementing only that, and let Teacher keep grading, meetings, and salary. Use delegation if the two share any real code.
  5. Verify: After refactoring, try to put a GuestSpeaker into endOfTerm's list. The compiler should now refuse — and a compile-time "no" is exactly the protection a runtime throw never gave you.

Frequently asked questions

What does the name Refused Bequest mean?
A bequest is what a parent leaves to a child as inheritance. In code, a parent class leaves its methods and fields to subclasses. Refused Bequest is when a subclass keeps the inheritance link but rejects most of what it inherited — unused methods, throwing overrides, ignored fields.
How is Refused Bequest connected to the Liskov Substitution Principle?
LSP says any subclass object must work correctly wherever the parent type is expected. A subclass that throws NotSupported on inherited methods breaks that promise: code written for the parent explodes when handed this child. Refused Bequest is the smell; the LSP violation is the injury.
Is it always wrong for a subclass to override a method to do nothing?
No. If doing nothing is genuinely correct behaviour for that subclass, the contract is still honoured and substitution still works. The smell becomes serious when the subclass refuses the parent's public contract — throwing errors or breaking expectations callers are entitled to have.
What is the main cure for Refused Bequest?
Replace Inheritance with Delegation. Instead of extending the parent, the class holds the parent (or a shared helper) as a private field and exposes only the methods it truly supports. Is-a becomes has-a, and there is nothing left to refuse.
Is there a famous real example of this smell?
Yes — java.util.Stack extends Vector, so a Stack inherits index-based insert and remove methods that break the last-in-first-out rule. The Java team themselves now recommend using Deque instead. Inheritance chosen for code reuse, not for a true is-a relationship.

Further reading

Related Lessons