Simplicity Meets Power: The Role of Variants in Software Development
Insights from Coffee Machine Comparisons to Software Design
Few months ago, I had a discussion with my friends about finding the best coffee machine. As we talked, I realized we couldn’t agree on which was better: the capsule coffee machine or the one that uses arabica beans. One friend said “It takes less than two minutes, and the taste is great !“. Another friend said, “The taste is better when you buy your own beans, and you can make milk foam yourself with this one.“.
They were both talking about coffee, but each had different expectations for the final product. Looking at the situation from another perspective, both sides had valid reasons to support their choice. In the end, we couldn’t find an answer that suited everyone because each of us was thinking based on their own needs. Each product meets a specific need, and we didn’t start with the same foundation. You might wonder what this has to do with software development or variants. That’s what we’re going to see in this article.

Abstraction
Is the coffee machine capable of consistently delivering the type of coffee I prefer?
How much control do I need over the coffee-making process?
Is the coffee machine straightforward to use for my preferred type of coffee?
Having the right context matters, especially when it comes to daily coffee drinking. Some people just want their coffee ready without dealing with complicated settings. It’s fine if these settings are preconfigured. All they want to do is make two simple choices: how strong they want their coffee and whether they want a large or small cup. They want the same coffee every time and aren’t interested in other options if it makes things more complicated.
Modularity
How flexible is the machine in adjusting settings or features like grind size, water temperature, or brewing time?
Are there interchangeable or adjustable components like different filters or milk frothers ?
Can the machine’s settings be easily adjusted for different coffee strengths, sizes, or temperatures?
Another type of user prefers a coffee machine that offers a lot of customization. They want to choose the type of coffee beans, set the water temperature, and make milk foam from scratch. This type of user enjoys having options and is willing to spend time learning how the machine works and adjusting settings to get the perfect coffee. However, with more customization comes more mental effort, as they need to manage every step of the process to ensure the coffee turns our great.
Encapsulation
Is the interface simple and focused on the key functions I need?
Can the machine handle different coffee preferences without requiring manual adjustments to its internal settings?
We’ve talked a lot about coffee, so let’s go a little deeper. When we talk about coffee, what we truly care about is the finished product and its flavor, but not everyone is interested in the details of how it’s created or the stages involved. If you think about it, all the questions we discussed about coffee can be applied in software development. When you take the smallest element of your application, and make changes on that atom, you create a variant, which is simply a different version of it, this idea is similar to encapsulation in design.
Encapsulation means hiding the complex details of how something works so that others don’t need to know them in order to use it. In coffee terminology, this means you won’t have to worry about the coffee machine’s inner workings. You only interact with what’s necessary through a well-defined interface, such as selecting the coffee strength and cup size.
This approach ensures that the details are kept within the system, promoting a clear separation of concerns. It allows interchangeability of the internal components or processes without affecting the external use of the machine. This modular design makes the system easier to use and adapt by focusing on exposing only what is meaningful to the user.
Just as choosing a coffee machine involves balancing simplicity and customization, so does creating components in software development. Some people prefer a simple, all-in-one machine because it’s easy to use, while others need a modular machine that offers more flexibility. Simplicity doesn’t mean there's no complexity, it’s about finding the right balance with flexibility.
Simplicity is the ultimate sophistication
Leonardo da Vinci
This suggests that true skill and beauty come from making things clear, simple, and free of unnecessary details.
As long as your interface is clear and you know what to expect, you can deliver a product that goes through a complex process. A user interface needs to be consistent and scalable, but whether you choose a straightforward approach or a modular one depends on what your project requires. Now let’s look at how these considerations apply to a user interface.
Example #1
I won’t present a list of best user interface libraries or compare by their pros and cons. There are already numerous resources and experts who do that. Instead, I’ll focus the need of keeping the appropriate level of abstraction and keep the design simple. By emphasizing encapsulation and modularity, we can create systems that are more maintainable and scalable. The goal is to highlight how thoughtful abstraction can lead to cleaner, more efficient code without getting bogged down in specifics of particular libraries.
The button component above has a clear interface that focuses on its purpose. You just need to choose the intent of the component. When you interact with it, you receive feedback, such as if the button has been pressed. The interface is simple and provides only what you need, without requiring you to know how button works internally. For example, you don’t need to know which icon or color corresponds to a given intent. This means we can switch out the user interface library without disrupting the system because everything is self-contained within the component.
Example #2
To illustrate the concept, consider an example of a booking system with basic components: Cart, Product List and Product Item. The Cart helps users buy items, the Product List shows products, like a showcase where users search for what they want. Each Product Item shows details like specifications and reviews. This is a basic example, but the main point is to show how we can create and use variants of the Product Item and the Button.
In this setup:
Cart ask the Product Item to display in summary mode
Product List asks the Product Item to display in grid mode
Product Page asks the Product Item to display in detailed mode
The look of Product Item component changes based on the selected mode, which is the only available interface. You can refer to the table for information on how each mode is displayed. This approach simplifies the process for the Cart, Product List and Product Page as they don’t need to specify individual details. You simply select one of three options, and the component will manage the rest behind the scenes.
If we did not spend time to identifying the different modes, we would have to create separate properties for each aspect of the component’s appearance. This would result in nine different properties, such as orientation, button visibility and so on. Worse still, this setup could lead to more than 700 possible combinations, many of which would be unintentional. Managing these combinations would have been complicated and error-prone.
TL;DR
🌠 The key takeaway from this post is the importance of keeping a clear separation of concerns and making sure your work is easy to understand for others. In software development, just like it is when selecting a coffee machine, balancing simplicity and customization is crucial. Always ask yourself about the role of your component, is it clear? Are implementations details hidden from the outside world? Is the component designed with a well-defined interface that acts as a contract?
Remember that naming and clarity matter, when using a component, you should understand what to expect from it, not how it works internally. Strive for a design that focuses on simplicity while accommodating the necessary complexity. By doing so, you create maintainable and scalable system that delivers value without unnecessary complications.
Further Reading & Inspirations
Vanilla Extract: Repices
Create multi-variant styles with a type-safe runtime API
Tool for managing CSS class names in React. It lets you easily set up and control style variations in your components