Deep Dive
low level designoopdesign principles

Liskov Substitution: If Clients Must Check the Type, You Broke It

An SmsChannel that can't do what a Channel promised isn't a safe stand-in for its base — so callers start type-checking. Liskov, explained simply: a subtype must work anywhere its parent does.

·8 min read
Medium

The nightly digest looped over every channel and called send(message, attachment). EmailChannel and PushChannel delivered fine. Then it reached SmsChannel, which threw the instant a message carried an attachment.

Look at what the loop was doing: it held a Channel and used it exactly the way the Channel contract allows. An SmsChannel is a Channel, so it should have been a safe stand-in — the loop was never supposed to care which channel it got. It had to. That broken substitution is the whole bug.

It’s the penguin problem in one sentence: a penguin is a bird, so any code expecting a bird should be able to use it — right until it calls fly(). A subtype that can’t do what its parent promised isn’t truly substitutable for it.

The members-only continuation runs the whole substitution test live — an interactive island where you toggle the naive and substitutable subtypes — then the before/after refactor code, the four contract rules a subtype may never break, the JDK’s own List.of LSP tension, the tradeoffs, and an interview corner with a coding challenge and quiz.

Members only

Keep reading with Premium

You've reached the members-only part of this deep-dive — the full implementation, the interactive ring simulator, and the step-by-step walkthrough. Unlock it with a membership.