Menu
Home Articles Bookmarks Experience Profiles About Work With Me
Abstract glowing connections representing universal contracts and interfaces
Software Engineering Apr 30, 2026 • 15 min read

Interfaces and Contracts: Every Wall Outlet Is a Promise

An interface says what an object can do without saying how. It's a wall outlet: plug anything in, and power flows. This lesson covers polymorphism, dependency inversion, duck typing, and why programming to abstractions changes everything.

Share:
Lee Foropoulos

Lee Foropoulos

15 min read

Continue where you left off?
Text size:

Contents

Complete Guide | Lesson 5: InheritanceLesson 6: Interfaces & Contracts → Lesson 7: SOLID (May 7)

Every wall outlet in your house makes the same promise: plug something in, and I'll give you power. A lamp, a blender, a phone charger, a vacuum cleaner. The outlet doesn't care. It promises 120 volts through a standardized socket, and anything that fits the plug can use it.

That's an interface. A promise of behavior without any specification of how that behavior is implemented.

This is Lesson 6, and it maps to Chesed on the Tree of Life. Chesed means "Mercy" or "Lovingkindness." It's the sephira of expansion, generosity, and giving without restriction. Chesed doesn't ask how you'll use the gift. It simply provides. An interface works the same way: it expands what objects can do without demanding how they do it. It gives power to any object that fulfills the contract.

What Is an Interface?

An interface defines what an object can do without specifying how it does it. It's a contract. A promise. A guarantee that says: "I can do these things. Ask me to do them. Don't worry about the details."

1INTERFACE IPayable
2    METHOD processPayment(amount: Decimal): Boolean
3    METHOD refund(transactionId: String): Boolean
4    METHOD getBalance(): Decimal
5END INTERFACE

Any class that implements IPayable must provide these three methods. How it provides them is its own business:

1CLASS CreditCard IMPLEMENTS IPayable
2    METHOD processPayment(amount)
3        // Contact card network, authorize, settle
4        RETURN success
5    END METHOD
6
7    METHOD refund(transactionId)
8        // Reverse the charge through the card network
9        RETURN success
10    END METHOD
11
12    METHOD getBalance()
13        RETURN this.creditLimit - this.usedCredit
14    END METHOD
15END CLASS
16
17CLASS PayPal IMPLEMENTS IPayable
18    METHOD processPayment(amount)
19        // Authenticate with PayPal API, deduct from wallet
20        RETURN success
21    END METHOD
22
23    METHOD refund(transactionId)
24        // Credit back to PayPal wallet
25        RETURN success
26    END METHOD
27
28    METHOD getBalance()
29        RETURN this.walletBalance
30    END METHOD
31END CLASS

CreditCard talks to a card network. PayPal talks to an API. Bitcoin would talk to a blockchain. Completely different implementations. Identical contract.

An interface doesn't care HOW you keep your promise. It only cares THAT you keep it. CreditCard, PayPal, and Bitcoin all implement IPayable. The checkout system calls processPayment() and trusts the contract.
Modular electronic components plugging into a universal connector system
A USB-C port doesn't care what's on the other end: a phone, a laptop, a monitor, a hard drive. It provides power and data through a universal interface. Any device that implements the spec works.

Polymorphism: Many Forms, One Interface

The word polymorphism comes from Greek: poly (many) + morphe (forms). It means treating different objects the same way through a shared interface.

Here's the magic. Your checkout system doesn't need to know whether it's dealing with a credit card, PayPal, or cryptocurrency:

1FUNCTION processCheckout(cart, paymentMethod: IPayable)
2    total = cart.calculateTotal()
3    success = paymentMethod.processPayment(total)
4    IF success THEN
5        PRINT "Payment processed!"
6        generateReceipt(cart, paymentMethod)
7    ELSE
8        PRINT "Payment failed. Try another method."
9    END IF
10END FUNCTION
11
12// Works with any IPayable implementation:
13processCheckout(myCart, new CreditCard("4111-1111-1111-1111"))
14processCheckout(myCart, new PayPal("[email protected]"))
15processCheckout(myCart, new Bitcoin("1A1zP1eP..."))

One function. Three completely different payment systems. Zero changes needed when you add a fourth. That's polymorphism.

Zero
code changes needed to add a new payment method when your system programs to an interface. Just create a new class that implements IPayable and plug it in.

Killing the If-Else Chain

Without polymorphism, adding payment methods looks like this:

1// Without interfaces - grows with every new payment type
2FUNCTION processPayment(type, amount)
3    IF type == "credit_card" THEN
4        contactCardNetwork(amount)
5    ELSE IF type == "paypal" THEN
6        callPayPalAPI(amount)
7    ELSE IF type == "bitcoin" THEN
8        broadcastToBlockchain(amount)
9    ELSE IF type == "apple_pay" THEN
10        callAppleAPI(amount)
11    // ...this keeps growing forever
12    END IF
13END FUNCTION

Every new payment method means modifying this function. Every modification risks breaking existing methods. This violates the Open/Closed Principle (which we'll cover in Lesson 7): open for extension, closed for modification.

With polymorphism, you never modify the checkout code. You just create a new class that implements IPayable and pass it in. The checkout doesn't know and doesn't care.

Before and After Polymorphism

Before: 47-line if/else chain that grows every time you add a feature. Every change risks breaking something. Testing requires hitting every branch. After: One method call that works for any implementation. Adding features means adding classes, not modifying existing code. Testing each implementation is independent. This is the difference between code that scales and code that collapses under its own weight.

Dependency Inversion: Depending on Promises, Not Details

Here's one of the most important principles in software design, and it comes directly from understanding interfaces:

High-level modules should not depend on low-level modules. Both should depend on abstractions (interfaces).

Without dependency inversion:

1CLASS CheckoutService
2    creditCard: CreditCard = new CreditCard()
3
4    METHOD checkout(cart)
5        this.creditCard.processPayment(cart.total)
6    END METHOD
7END CLASS

The CheckoutService is welded to CreditCard. Want PayPal? Rewrite the service. Want to test without charging real cards? Can't.

With dependency inversion:

1CLASS CheckoutService
2    paymentMethod: IPayable
3
4    CONSTRUCTOR(paymentMethod: IPayable)
5        this.paymentMethod = paymentMethod
6    END CONSTRUCTOR
7
8    METHOD checkout(cart)
9        this.paymentMethod.processPayment(cart.total)
10    END METHOD
11END CLASS
12
13// Inject any implementation:
14service = new CheckoutService(new CreditCard(...))
15service = new CheckoutService(new PayPal(...))
16service = new CheckoutService(new FakePayment())  // For testing!

The CheckoutService depends on the interface, not the implementation. You can swap payment providers without touching the service. You can inject a fake payment object for testing that never charges real money. The service doesn't know the difference, and that's the point.

Depend on abstractions, not concretions. Your checkout shouldn't know whether it's talking to Visa or Bitcoin. It should know it's talking to something that can process payments. The interface is the firewall between your logic and its dependencies.
Modular system with interchangeable components slotting into a framework
Dependency inversion turns your code into a system of slots and plugins. The framework defines the slots (interfaces). Implementations plug in. Swap one plugin for another without touching the framework.
80%
reduction in testing complexity when code depends on interfaces instead of concrete classes. You can inject mock implementations for every dependency and test in isolation.

Duck Typing: If It Quacks Like a Duck

Not every language has a formal interface keyword. Python, JavaScript, and Ruby use what's called duck typing: "If it walks like a duck and quacks like a duck, it's a duck."

In duck-typed languages, you don't declare "I implement IPayable." You just provide the methods that the caller expects:

1// Python-style duck typing
2class CreditCard:
3    def process_payment(self, amount):
4        # process credit card
5        return True
6
7class PayPal:
8    def process_payment(self, amount):
9        # process PayPal
10        return True
11
12def checkout(payment_method, amount):
13    # Doesn't check the type. Just calls the method.
14    payment_method.process_payment(amount)

If payment_method has a process_payment() method, it works. If it doesn't, it crashes at runtime. No compiler to catch the mistake.

Structural typing (TypeScript, Go) is a middle ground: the compiler checks that an object has the right shape (methods and properties) without requiring an explicit "implements" declaration.

Nominal typing (Java, C#) requires you to explicitly write class X implements Y. The compiler enforces the contract at compile time.

Typing Systems Compared

Duck typing (Python, JS, Ruby): No formal interfaces. If the method exists, it works. If not, runtime crash. Maximum flexibility, minimum safety. Structural typing (TypeScript, Go): The compiler checks the shape, not the declared type. If it has the right methods, it fits. Good balance. Nominal typing (Java, C#): You must explicitly declare which interfaces you implement. Maximum safety, slightly more verbose. Each has trade-offs. The concept of programming to an interface applies regardless of which system your language uses.

"Program to an interface, not an implementation." This advice from the Gang of Four's Design Patterns (1994) is arguably the single most impactful sentence in the history of software design. Every pattern, framework, and architectural principle that followed builds on this foundation.

Different birds performing the same action of flying despite different appearances
An eagle, a sparrow, and a hummingbird all implement IFlyable. Completely different sizes, speeds, and techniques. One interface, many forms. That's polymorphism in nature.
Lesson 6 Practice 0/6
How was this article?

Share

Link copied to clipboard!

You Might Also Like

Lee Foropoulos

Lee Foropoulos

Business Development Lead at Lookatmedia, fractional executive, and founder of gotHABITS.

🔔

Never Miss a Post

Get notified when new articles are published. No email required.

You will see a banner on the site when a new post is published, plus a browser notification if you allow it.

Browser notifications only. No spam, no email.

0 / 0