Loose and Tight Coupling
What is loose and tight coupling?
When talking about coupling, we are talking about how much the components in a system know about each other. Are they dependent on others to work? What I mean by component is a limited area of your system that can represent or manipulate data, like a class in object-oriented languages. A system is just your entire code base with all your components in it.
A component is tight coupled if it knows about other components in the system to the degree it depends on them to function. If you cannot replace B with C without A breaking, A is tightly coupled with B.
A component is loosely coupled if it only knows about itself. In theory, you can remove it from the system without it breaking. The system may need a replacement to work again, but that is fine and easy to do with loose coupling.
Why we want loose coupling?
The main reason I like it is that it makes it easy to replace a component. I often refactor after learning new tricks and after spending time with a system. Refactoring is a lot faster with loose coupling. I have worked on so tightly coupled systems that we could not remove old components because it would be too much work having to remove and fix broken references.
It is easier to scale your system. Because your components work independently, they can scale independently. Having to scale your entire system when only a tiny part of it needs the extra resources is a waste. The ease of scaling is essential when working on microservices.
Because components are self-contained, you are going to have less conflict when merging. Team members can work on two components that talk to each other without worrying about renaming variables and functions.
Tests are easier to write with a loosely coupled system. You can instantiate only the specific components needed, mock the entry and exit points, or create a set of replacement components only used for testing.
The disadvantage of loose coupling
It will add complexity to your system, no two ways about it. You are going to have to be more abstract in the way you describe things. You will also need to spend more time setting everything up the first time.
You are limited in when, how, and how much data you can share. The time of making things public and accessing them whenever you want is over. You have to plan and structure the way you share information between components. You should probably be doing this anyway.
If you are developing a very domain-specific system and know exactly what you need, you might get more performance by having a tightly coupled system.
Most of the time, you want to have loose coupling. It might seem easier in the moment to go with tight, but trust me, it rarely turns out more manageable in the long run. Remember that it is easier to go from loose to tight than the other way around, so there is no reason not to start with loose if possible.
How to achieve loose coupling
The way you achieve loose coupling depends on your language, but it boils down to having a standardized form of communication. Components no longer know who they are explicitly talking with; all they know is that the other component follows the agreed standard of communication.
Here is a simplified example. Let us say you have some Bluetooth headphones, a smartphone, and a computer. Your headphones do not need to know that it is connected to a phone to receive music. It just needs a device that follows the Bluetooth protocol. The same applies the other way around. You can swap between using your phone or computer or even add your car as a device. You can also remove the headphones without the phone breaking or replace them with some new ones. Nobody in our system of phones, computers, cars, and headphones needs to know each other. They only need to know how to talk using Bluetooth.
If you want a code example of loose coupling, I have written a post with an example in C# using events and interfaces here.