I started exploring Erlang mainly to better understand the actor model. Very quickly, I realized that in Erlang this model is so deeply integrated into the language and runtime that it is rarely explicit or “observable”, it simply is the way the system works.
Paradoxically, once I reached a point where I could start testing things, it felt like I had to learn Erlang backwards: many of the problems it solves are problems you only fully understand after you’ve already built large distributed systems in other ecosystems.
Over time, I began to see Erlang not just as a programming language, but as a set of well-established practices for building reliable, distributed systems, supported by a language and runtime designed specifically for that purpose.
Erlang feels self-sufficient in a way few ecosystems do.
Message passing and process isolation are built into the language and runtime. Many problems that would require external systems (for example, message brokers) in other stacks can often be solved directly within Erlang/OTP.
This doesn’t mean Erlang replaces tools like Kafka in all cases — but it does mean you should first ask why you need them if you are already using Erlang.
Erlang supports spawning millions of lightweight BEAM processes. These are not OS threads, but extremely cheap, isolated processes that can progress independently and fault independently, supervised by OTP.
Erlang excels at coordination, routing, fault tolerance, and concurrency, but it is not always the most readable or ergonomic choice for complex parsing or heavy business logic.
For that reason, I personally see Erlang working best when kept focused and small:
To me, Erlang is best understood through OTP, which plays a role similar to frameworks like Spring in the Java world.
Typical OTP structure:
* Application – manages the lifecycle of the system
* Supervisor – monitors and restarts workers
* Workers – gen_server, gen_statem, gen_event
Supervision in Erlang feels like Kubernetes-style resilience, but embedded directly into the language runtime instead of being delegated to infrastructure.
Reading Erlang from a Java background, I sometimes think of it as combining concerns that are usually split across:
* Spark (distributed computation),
* Kubernetes (process supervision),
* Kafka (message-based interaction),
but inside a single coherent runtime model.
This analogy is imperfect, but it highlights why Erlang feels unusually complete as a platform.
All of this reflects a learning journey, not production experience.
I have not yet delivered a production system in Erlang. While following this ChatGPT-generated course, I ran into real issues, especially around library versions and integrations. Those moments involved repeated debugging and research cycles, which can become tedious over time.
This course is not about isolated Erlang examples.
Instead, it is structured as preparation for a production-like delivery:
The final goal is a distributed chat system built with OTP and Mnesia, deployed using Docker.
I intentionally avoided installing Erlang locally and worked entirely through Docker (erlang:27) to stay close to a production-style workflow.
Course Structure
./docker/scripts/run_tests_wsl.sh –dev
docker run -it --rm -v $(pwd):/app -w /app erlang:27 bash
erlc -o . ./src/*.erl
or this way for a single file:
erlc MyModule.erl
erl -pa . -s MyModule hello
In the early chapters, functionality is validated through automated tests. In the later stages, I chose to execute and observe the system manually to gain a deeper understanding of how the processes interact.
Although automated tests are usually the most effective learning tool, some uncertainty during development pushed me to rely more on hands-on experimentation.
I’m satisfied with how far this project went. From here, the sandbox is ready - future work is about refinement, tuning, and real-world experience.