At one point, my brain simply refused to understand what was happening. I was close to giving up on the projects required for the RNCP 6 application development track at École 42.
But I did not stop. Eventually, I passed two projects written in OCaml.
The result was more valuable than learning another syntax. OCaml forced me to question assumptions I had carried from imperative programming. It changed how I think about expressions, state, data modeling, and the role of the compiler.
Moving to OCaml felt like riding a normal bicycle for years and then suddenly being asked to ride a unicycle. The basic purpose still looked familiar: write a program, represent data, transform inputs into outputs. But the balance was different, and many automatic habits no longer worked.
The first confusion: what does let actually mean?
In languages such as C, JavaScript, or Python, it is easy to develop an intuitive model in which a name is a box that contains a value. We create the box, change what is inside it, and continue.
Then OCaml introduces let.
Is let a variable declaration? Not exactly. Is it a function? Not exactly either. It binds a name to an expression. That sentence sounds simple, but understanding it required me to revisit basic terminology from both algebra and programming.
An expression is something that evaluates to a value. A binding gives that value a name within a scope. Once I started looking at code through expressions rather than instructions, the structure of programs began to feel different.
Instead of asking, “Which variable should I modify next?” I started asking, “What value should this expression produce?”
That shift reduces the number of hidden transitions a reader must keep in their head.
State is more than an enum
The next challenge was understanding variants and state.
At first, algebraic data types looked like a more complicated version of enums. But that comparison is incomplete. In OCaml, variants can describe not only a list of labels but also the precise data associated with every possible state.
A process might be waiting, running with a job identifier, completed with a result, or failed with an error. Instead of storing a status string next to several optional fields, the type can express which data is valid for each state.
That gives the compiler useful information. If every variant must be handled, an impossible or forgotten state is more likely to become a compile-time problem instead of a production incident.
This idea has direct relevance beyond functional programming. Infrastructure systems are full of state machines: deployments, jobs, health checks, failovers, incident workflows, and authentication flows. When states are represented vaguely, edge cases hide in the gaps. When states are explicit, the system becomes easier to reason about.
Pattern matching is executable documentation
Pattern matching initially felt ceremonial. Why write several branches when a familiar conditional might be shorter?
The answer became clearer as the programs grew. A pattern match shows the reader the complete shape of a decision. It connects possible data forms directly to behavior. The compiler can also warn when a case is missing.
That combination is powerful:
- the type describes what can exist;
- the pattern match describes what happens for each possibility;
- the compiler checks whether the description is complete.
The code becomes a form of executable documentation. It does not eliminate logical mistakes, but it makes entire categories of incomplete thinking more visible.
Less mutation requires more clarity
The functional approach was painful at first because mutation is convenient. When a program is not working, changing a value “right here” can feel like the fastest path forward.
OCaml repeatedly removed that escape route.
Less mutation meant less “I will just update this value and see what happens.” I had to describe transformations more deliberately. Data flowed from one expression to another. Functions became easier to test in isolation because their behavior depended less on invisible state elsewhere in the program.
This discipline can feel slower during the first hours of a project. In return, it reduces the time spent asking how a value reached its current state.
That trade-off is familiar in platform engineering. A manual production change may be faster once. Infrastructure as code asks for more structure up front, but the resulting history, reviewability, and repeatability pay for that effort many times over.
The compiler became a collaborator
Before these projects, I often experienced the compiler mainly as a gatekeeper: it accepted code or rejected it.
OCaml made the compiler feel more like a collaborator. Strong types and exhaustive checks pushed design feedback earlier in the process. A compilation error was often evidence that my mental model was incomplete.
This does not mean that strongly typed functional programs cannot fail. They can. Networks time out, requirements change, and incorrect assumptions still compile. But a helpful type system narrows the space of possible mistakes before runtime.
For systems that need to be dependable, moving feedback earlier is valuable. A mistake found during editing is cheaper than one found during testing. A mistake found during testing is cheaper than one found during an incident.
The real outcome was a new layer of thinking
After passing the two projects, I did not suddenly become an OCaml expert. What changed was the set of questions I ask when reading or designing code.
I now pay more attention to:
- whether a value should be mutable at all;
- whether possible states are represented explicitly;
- whether invalid combinations can be made impossible;
- whether a function describes a transformation clearly;
- whether the compiler can verify more of my assumptions;
- whether runtime branching reflects a complete model of the data.
I started seeing code not only as a sequence of instructions, but as a system of expressions, types, and states.
The experience was difficult. That difficulty was the point. Some technologies are valuable not because they become part of your daily stack, but because they expose the limitations of your current way of thinking.
OCaml did that for me. It added another lens through which I can evaluate software, infrastructure tools, and production systems. Even when I return to Python, Go, TypeScript, or shell scripts, the questions remain.
That is one of the best outcomes education can produce: not simply another language on a résumé, but a broader way to reason about engineering.