What a design pattern is, and how Kotlin changes the calculus
Design patterns are named solutions to problems that come up again and again in software. Learning them gives you a shared vocabulary — say "factory" or "observer" and another developer knows what you mean — and a toolbox of proven structures. But there is a twist that most pattern courses miss, and that this module is built around: many of the classic patterns exist to work around limitations of older languages, and Kotlin makes them disappear.
Where the patterns came from
The famous catalogue — the "Gang of Four" patterns: Singleton, Factory, Builder, Adapter, Decorator, Strategy, Observer, and the rest — was written in 1994, in and for C++ and Java. Those languages lacked features we now take for granted: functions were not values, there were no lambdas, no first-class support for delegation, no null safety, no data classes. Many "patterns" are, at heart, ceremonies for doing something the language could not do directly.
The Strategy pattern, for instance, is "pass different behaviour into an object" — which in Java required defining an interface, writing a class per strategy, and wiring them up. That is a lot of structure for an idea Kotlin expresses in one word: a lambda. The pattern has not become wrong; it has become a language feature.
Kotlin changes the calculus
Kotlin has, built in, many of the things the patterns were invented to simulate:
- Functions are first-class values — so "pass behaviour around" (Strategy, Command, parts of Observer) is just passing a lambda. No interface, no class hierarchy.
objectdeclarations — so Singleton is one keyword, not a careful thread-safe idiom.- Default and named arguments — so Builder (constructing an object step by step) is often unnecessary; you just call the constructor with the arguments you want.
- Extension functions — so Adapter and Decorator (adding or adapting behaviour) frequently become a small extension, not a wrapper class.
- Delegation with
by— first-class support for composition-over-inheritance, which several structural patterns approximate by hand. - Sealed classes and
when— so State and some Visitor uses become an exhaustivewhenover a sealed hierarchy. - Data classes — so value objects and DTOs need no boilerplate.
The consequence, and the theme of this module: in Kotlin, you should reach for a language feature
before you reach for a pattern. A junior developer, having just learned the patterns, tends to
apply the heavy Java form — an interface, a factory class, a builder — where a lambda, an object, or
a default argument would do the same job in a fraction of the code. Recognising when the language has
already solved the problem is the mark of someone who understands both the patterns and Kotlin.
Why still learn the patterns, then?
Three solid reasons, so this is not "ignore the patterns":
The vocabulary is real and useful. When you say "I made the repository a singleton" or "this is a strategy", every developer understands instantly. The names are shared knowledge across the whole industry, and you will read them in code, documentation, and interviews. You must know what they mean, even when you implement them differently.
The underlying problems are eternal. "How do I vary behaviour without duplicating code?" "How do I notify many parts of the app when something changes?" "How do I construct a complex object safely?" Those questions do not go away — only the mechanics of answering them change. The patterns teach you to recognise the problems, which is more valuable than any specific implementation.
Some patterns still earn their place in Kotlin, in a lighter form. A factory function is genuinely
useful. The observer idea underlies Flow. Delegation is a first-class feature precisely because
composition-over-inheritance is right. You will use these — just written the Kotlin way.
How this module is organised
The rest follows the classic three categories, each showing the Kotlin reality:
- Creational (how objects are made) — Singleton, Factory, Builder. Mostly replaced by
object, factory functions, and default arguments. - Structural (how objects are composed) — Adapter, Decorator, and delegation. Mostly extensions
and
by. - Behavioural (how objects interact) — Strategy, Observer, State. Mostly lambdas,
Flow, and sealed classes.
And a final lesson on the most important skill of all: knowing when a pattern is the wrong answer — because over-applying patterns produces code that is more complex, not less, and that is a more common failing among enthusiastic juniors than under-using them.
The rule to carry through
For every pattern in this module, ask two questions:
- What problem does it solve? (This is eternal — learn it.)
- Does Kotlin already solve it more simply? (Very often — use that.)
A pattern applied where the language already had an answer is not sophistication; it is over-engineering. A pattern applied where it genuinely fits, in the lightest form the language allows, is good design. Telling the two apart is what this module teaches.
Check your work
What a design pattern is. A named, reusable solution to a recurring design problem — a shared vocabulary and a toolbox.
Where the classic catalogue came from. 1994, for C++ and Java, which lacked lambdas, first-class
functions, object, delegation, and null safety.
Why many patterns exist. As ceremonies to do what older languages could not do directly.
Kotlin's core message about patterns. Reach for a language feature before a pattern — the language often already solves the problem.
Features that replace patterns. First-class functions (Strategy/Command), object (Singleton),
default/named arguments (Builder), extensions (Adapter/Decorator), by (delegation), sealed classes
(State).
Three reasons to still learn the patterns. The vocabulary is shared industry knowledge; the underlying problems are eternal; some patterns still earn their place in lighter form.
The two questions to ask of every pattern. What problem does it solve, and does Kotlin already solve it more simply?
The common junior failing. Applying heavy Java-style patterns where a lambda, object, or default
argument would do — over-engineering.
Practice
- List five patterns you have heard of. For each, guess what problem it solves before this module tells you.
- Explain, in one sentence, why "functions are values" makes the Strategy pattern nearly disappear.
- Find a piece of Java or old code (online is fine) that uses a Builder or Factory, and consider how Kotlin's default arguments or a factory function would shorten it.
- Write down the two questions to ask of every pattern, and keep them beside you for the rest of the module.
- Recall a time you (or code you have read) added structure that turned out to be unnecessary. Note what the simpler version would have been.
Official documentation
- Kotlin — Idioms — Many of which are the Kotlin form of a classic pattern.
- Kotlin — Object declarations — Singleton as a language feature.
- Kotlin — Delegation — Composition-over-inheritance built in.
- Refactoring Guru — Design Patterns — A clear, language-agnostic reference for what each pattern is (implement them the Kotlin way).
Next: creational patterns — Singleton, Factory and Builder, mostly replaced by the language.
Stuck on this lesson?
Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.
About the internship