WorkspaceLow-Level Design
← Course overview

Course navigation

On this page

Low-Level Design / SOLID Principles

foundation · 12 MIN READ

Liskov Substitution

A subtype must preserve the expectations established by its base contract. Matching method names is insufficient if an implementation strengthens preconditions, weakens guarantees, or breaks invariants.

Reading completion is independent of quizzes and challenges.

What you’ll understand

  • A subtype must preserve the expectations established by its base contract. Matching method names is insufficient if an implementation strengthens preconditions, weakens guarantees, or breaks invariants.
  • Check every withdrawal condition before mutating the account, then return the agreed success flag.
  • Evaluate the tradeoff: Contracts must be explicit enough to test. If a subtype cannot honor an operation, model a smaller capability instead of returning surprising values or throwing for ordinary base-class inputs.

Useful first: Classes and Objects

The idea and when to recognize it

A subtype must preserve the expectations established by its base contract. Matching method names is insufficient if an implementation strengthens preconditions, weakens guarantees, or breaks invariants.

  • Requirement: A caller uses account.withdraw and expects a rejected withdrawal to leave the balance unchanged.
  • Change to anticipate: Apply a reserve policy without allowing a rejected operation to consume money.
  • Design goal: Check every withdrawal condition before mutating the account, then return the agreed success flag.

Start with the concrete problem

A caller uses account.withdraw and expects a rejected withdrawal to leave the balance unchanged.

Apply a reserve policy without allowing a rejected operation to consume money.

Direct implementationRevised design
Mutating the balance before discovering that a reserve rule rejects the withdrawal violates the no-change-on-failure guarantee.Check every withdrawal condition before mutating the account, then return the agreed success flag.
The baseline case can hide the problem. Compare the extension and boundary fixtures before accepting this design.Contracts must be explicit enough to test. If a subtype cannot honor an operation, model a smaller capability instead of returning surprising values or throwing for ordinary base-class inputs.

Responsibilities and contracts

Check every withdrawal condition before mutating the account, then return the agreed success flag.

  • Account: Promises unchanged state on rejection. Public operations: withdraw(amount)
  • ReserveAccount: Adds a configured reserve within the agreed policy. Public operations: withdraw(amount)

Read the design

Select a participant to inspect its responsibility and public operations. Follow the labeled relationships; an arrow indicates direction, while the legend distinguishes ownership, substitution, and temporary use.

Preparing the design walkthrough…

Follow the interaction

Choose a supplied workload and compare the direct and revised implementations. Step through the same operation in both languages. Object values are recorded after each highlighted statement; previous steps remain unchanged.

Preparing the design walkthrough…

Implementation and expected output

Read the complete example alongside its input and expected output. The implementation selector changes the displayed program; the language selector preserves your current scenario and playback position.

Preparing the design walkthrough…

Try the idea in another setting

A storage reader subtype must preserve the same end-of-input behavior as the reader contract.

  • Name the new client contract before adapting the example.
  • Identify which state belongs to each object and who controls its lifetime.
  • Write one normal case and one boundary case before extending the design.

Tradeoffs and interview takeaways

Contracts must be explicit enough to test. If a subtype cannot honor an operation, model a smaller capability instead of returning surprising values or throwing for ordinary base-class inputs.

Common mistake: Mutating the balance before discovering that a reserve rule rejects the withdrawal violates the no-change-on-failure guarantee.

  • Explain the choice: Check every withdrawal condition before mutating the account, then return the agreed success flag.
  • Challenge your choice: Contracts must be explicit enough to test. If a subtype cannot honor an operation, model a smaller capability instead of returning surprising values or throwing for ordinary base-class inputs.
  • Describe a requirement that would make the simpler implementation sufficient.

Design challenge and knowledge check

Preparing the design walkthrough…

Make it your own

Saved on this device · 0/20,000

Reading completion is independent of quizzes and challenges.