Before you start
DIP is the last SOLID letter, and the one that most changes how you WRITE code once you get it. Every class you build after this will take its dependencies as constructor arguments instead of creating them inside.
The one-line version
Depend on interfaces, not on specific classes. Pass dependencies in instead of building them inside.
Why this is the highest-leverage principle
Every earlier principle improved the shape of a single class. DIP improves how classes CONNECT.
Watch what changes when you follow DIP consistently:
- Testing. Every class becomes testable with fakes / mocks / stubs because you pass the dependency in from outside.
- Reuse. The same
OrderServiceworks with email, SMS, push, or a queue — just pass a different notifier. - Swappable. The day you switch from Twilio to MessageBird, you replace ONE thing (the notifier). Every caller stays the same.
- Fast tests. No real HTTP calls in unit tests. Test suite runs in seconds instead of minutes.
For any codebase of real size, this is the difference between "tests we trust" and "tests we skip".
The flip, in a picture
Before DIP:
OrderService --calls--> EmailNotifier --calls--> SMTP
(high-level) (low-level)
OrderService knows about EmailNotifier. If you swap
SMTP for an API, both classes change.
After DIP:
OrderService --depends on--> Notifier <--implements-- EmailNotifier
(high-level) (interface) (low-level)
Both high-level (OrderService) and low-level
(EmailNotifier) point at the interface. Neither knows
about the other. That's the flip.
In interviews
Interviewers use DIP as a probe by asking "how would you test this?"
- Weak answer: "I'd mock the email library with unittest.mock at the module level."
- Strong answer: "The notifier is passed in. In the test I pass a fake notifier that captures the calls."
The second answer shows design maturity. It also works everywhere: every dependency (database, HTTP client, clock, random) should be passed in the same way.
About the strict test
The last test in this lesson reads your constructor's
source code and fails if it names EmailNotifier or
SmsNotifier. This is the strictest possible check —
your constructor should only mention the abstract
Notifier parameter and store it. Any specific type
leaking in is DIP breaking down.
The end of SOLID
After this lesson you have every SOLID principle. The next lesson (the refactor drill) makes you use all five on a broken codebase — the hardest lesson in the module by design, because that's the interview shape.