Hello! Welcome to the first lesson in our course on Go interfaces.
Given your interest in the "why" behind programming language features, this first lesson is designed to establish the foundational problem that interfaces were created to solve. By the end of our 60 minutes, you will be able to explain how interfaces solve the problem of tight coupling by defining abstract behavior contracts between software components.
We will start by defining the core software engineering challenge of coupling and then explore how interfaces provide an elegant solution, a cornerstone of Go's design philosophy.
1. The Core Problem: Tight Coupling
In any complex system, whether it's a software application, an engineering project, or even a large organization, the various parts need to interact. The way these parts are connected is a critical aspect of the system's design.
Imagine building a custom computer where the CPU is permanently soldered to a specific model of motherboard, which in turn only works with one exact type of RAM and a specific brand of power supply. This system is tightly coupled. If the power supply fails, you can't just swap in a standard replacement; you need the exact original model. If you want to upgrade your RAM, you might have to replace the entire motherboard and CPU. The components are too dependent on the specific implementation details of each other.
Now, contrast this with a standard PC. The components are designed to meet public specifications, or contracts. A power supply has a standard ATX connector. RAM modules have standard DDR5 slots. A graphics card uses a PCIe slot. You can mix and match components from different manufacturers because they all adhere to the same contract. This is loose coupling.
In software, tight coupling means that different parts of your code are highly dependent on each other. A change in one component often requires changes in many others. Loose coupling, the desired state, means components are independent and interact through well-defined "plugs" or "ports," making the system flexible, maintainable, and easier to test.

Let's consider a practical software example. Imagine your application needs to store data. You write your code to work directly with a PostgreSQL database. Your code is filled with references to the PostgreSQL driver, its specific functions, and its data types. This application is now tightly coupled to PostgreSQL. What happens when your boss tells you the company is switching to MySQL for cost reasons? You would have to go through your application and change every part that interacts with the database.
This is the problem interfaces are designed to solve.
2. The Solution: Abstract Behavior Contracts
Instead of making our application depend on a concrete database implementation (like PostgreSQL), we can make it depend on an abstraction—a contract that defines the behavior we need from a database.
This is exactly what an interface is: an abstract behavior contract. It defines a set of methods that a type must have to fulfill the contract, without dictating how those methods should work.
To explore this idea, let's watch a segment from the video "Master Golang with Interfaces." The speaker uses a fantastic analogy of a physical audio interface to explain this concept of a contract, before applying it to the database problem we just discussed.
Please watch the following sections from the video by Kantan Coding. It introduces the problem of coupling with a database example and uses a physical analogy to explain how an interface acts as a contract between an application and its dependencies.
Watch from the timestamp 01:25 to 08:21. Focus on understanding: The problem of an application needing to connect to various database types. The audio interface analogy: The 'port' is the contract, and any device with the right 'plug' can connect. How this translates to programming: The interface defines methods (the contract), and any database type that implements those methods can be 'plugged in' to the application.
As the video explains, the application doesn't need to know the internal details of PostgreSQL or MySQL. It only needs to know that it can call Close() and SelectUser(). The interface is the "port," and any database that provides the correct "plug" (by implementing the required methods) can connect. The application code interacts with the abstract contract, not the concrete implementation.

This design choice was very deliberate by Go's creators. They had witnessed the challenges of building large-scale software with traditional object-oriented languages that rely heavily on inheritance, which often leads to tight coupling and rigid type hierarchies.
The following article, "Go at Google," written by one of Go's designers, provides insight into this philosophy.
Go at Google: Language Design in the Service of Software ...
This article explains the design philosophy behind Go's approach to object-oriented programming. It argues for composition over inheritance and highlights why interfaces are a more flexible tool for building large systems.
Please read the sections starting with 'Object-oriented programming provides a powerful insight...' and ending just before the 'Wrappers' subsection. Focus on the distinction Go's designers make between generalizing behavior versus being locked into a type hierarchy.
The key takeaway from that reading is that by focusing on what things do (their behavior, defined by interfaces) rather than what things are (their type, defined by a class hierarchy), Go enables a more adaptable and scalable way of building software. Components can be composed and connected in novel ways, just like the Unix philosophy of chaining small, single-purpose tools together.
3. Decoupling in Action
Let's see a concrete example of how this works in Go and the flexibility it provides. The io.Writer interface from Go's standard library is a classic and powerful example. The io.Writer interface contract is very simple; it requires only one method:
type Writer interface {
Write(p []byte) (n int, err error)
}
Anything that fulfills this contract can be used anywhere a Writer is expected. This includes files, network connections, in-memory buffers, and even an HTTP response.
The next video demonstrates this by refactoring a function that is initially tightly coupled to writing to a file, making it far more flexible by using the io.Writer interface.
What Makes Golang Go: The Power of Go Interfaces — Ricardo Gerardi
This segment from 'What Makes Golang Go' provides a practical demonstration of decoupling. Watch how a 'save' function evolves from only being able to work with files to being able to work with any type that satisfies the 'io.Writer' interface.
Watch the section from 20:30 to 26:27. Pay close attention to how the save function's signature changes from func save(f *os.File, ...) to func save(w io.Writer, ...). Notice how this single change allows the same function to write to both a file and a network connection without any other modifications to the function's logic.
This example perfectly illustrates the power of decoupling through interfaces.
- Initially,
save(*os.File)was tightly coupled. The function's dependency was a concrete type: a pointer to a file. Its utility was limited to that single purpose. - After refactoring,
save(io.Writer)is loosely coupled. The function now depends on an abstract behavior: the ability toWrite. It can now be used with files, network connections, or any other type—even types that haven't been invented yet—as long as they satisfy theio.Writercontract.
This is the essence of why interfaces are a fundamental feature in Go. They are the primary tool for building clean, modular, and maintainable systems.
Conclusion
In this lesson, we established the core motivation behind Go's interfaces. We started with the problem and ended with the elegant solution Go provides.
Key Takeaways:
- Tight Coupling: Occurs when software components are highly dependent on each other's specific implementations. This makes systems rigid, hard to maintain, and difficult to test.
- Abstract Behavior Contract: An interface defines a set of methods (behaviors) without specifying the underlying implementation. It's a contract about what a type can do, not what it is.
- Loose Coupling: By having components depend on interfaces rather than concrete types, we create loosely coupled systems. This allows implementations to be swapped, extended, or replaced without breaking the components that rely on them.
- Go's Philosophy: This approach reflects a deliberate design choice in Go to favor composition and behavioral abstraction over rigid type hierarchies, leading to more flexible and scalable software.
Preview of the Next Lesson
Now that we've covered the "why," our next lesson will focus on the "how." We'll explore a unique and powerful feature of Go's design: implicit interface satisfaction. We'll examine why Go doesn't require an implements keyword like other languages and how this design decision further promotes the decoupling we discussed today.