Tarides Logo
<optional, will be used to describe the image for screen readers>

What is Functional Programming? A Look at the Programming Style from an OCaml Perspective

Posted on Wed, 29 Apr 2026

You have seen the social media debates: one person says functional programming is dead and no one cares, the other says it could solve every problem you ever have. How do you know what to think?

Reality is (as usual) not quite so simple, and the evidence is present in the mainstream languages you probably use every day. For example, Python supports list comprehensions and higher-order functions, C# has introduced lambdas, streams, and pattern matching, and Rust, a language deeply influenced by FP design, is widely admired by developers.

With the increased attention, many developers and aspiring programmers are curious to learn more about this programming style. This post will introduce you to FP, how it differs from other styles, its benefits and considerations when using it, and use cases where it has proven successful.

We’ll be using OCaml in our examples today because, well, it’s kind of our thing!

What is Functional Programming: By Definition

Let’s start with a formal definition. Functional programming is a “programming paradigm where programs are constructed by applying and composing functions”. But what does that mean?

Essentially, it is a style of writing code in which functions are composed of other functions and (almost) everything is an expression rather than a statement with side effects. Given the same inputs, functions always return the same output regardless of the number of function calls. It solves the problem of calling a function and having something unexpected happen somewhere else in your code.

What is Functional Programming: In Practice

In reality, however, the definition of functional programming is a bit more fluid. There are dozens of categories that you can use to describe programming languages, and many of them fall under more than one.

If we choose to briefly look at five major categories, these are procedural programming, which relies on a sequence of statements or commands to get a desired result; functional programming, which uses mathematical functions; object-oriented programming (OOP), which uses objects made up of data and program elements to build programs; scripting languages, which automate repetitive manual tasks; imperative programming, which relies on statements and commands to create step-by-step instructions to achieve results; and logic programming, which is based on formal logic that is used to present information about a problem.

In addition to these major categories are many many sub-categories and additional groups of definitions. It is important to bear in mind that in real life, a computer programming language can be hard to define as a single ‘thing’.

What Does the ‘Purest’ Form of Functional Programming Look Like?

The first functional programming language, which still forms the basis of all FP languages, was the Lambda Calculus created in the 1930s. Since then, many more have joined the ranks, all sharing a strong mathematical foundation that has evolved with the advent of modern computers and computer science.

From its origins, FP has evolved to include a few core concepts, which include:

  • Pure functions: A pure function always returns the same output for the same input, no matter when you call it, and its return value has no side effects.
  • Immutability: Once a value is created, it cannot be changed by a function. Instead of modifying existing data in place, the program creates new data and leaves the original unchanged.
  • First-class functions: Functions are treated like any other value and can be passed as arguments, returned from other functions, and stored in variables and data stores.
  • Type systems: A set of rules that the compiler uses to classify every value in a program.
  • Recursion: Iteration is achieved via recursive functions that invoke themselves.

Now that we have an idea of what functional programming looks like in theory, let’s move on to how programming languages use it in practice.

What Does Functional Programming Really Look Like?

Most programming languages combine features and principles to achieve a unique design that suits their use case.

Haskell is an example of one of the purest functional programming languages, where side effects are controlled through the type system using monads, and everything stays immutable. Scala blends FP and OOP, including features of the former like currying, immutability, and pattern matching, but also uses a curly-brace syntax to be object-oriented. Rust is another multi-paradigm language that incorporates FP features like immutability and pattern matching with support for OOP through structs, enums, and methods.

Let’s take a closer look at another language that strategically blends different paradigms! OCaml is not a purely functional language, and, for example, does not enforce purity but allows you to write pure code and code with side effects. This is a deliberate trade-off since some side effects are very useful. For example, I/O operations like printing, reading files, and making network calls normally involve side-effects, and OCaml lets you do this without needing to wrap everything in a monad. Interoperability with C or the operating system is another scenario where side effects are hard to get around cleanly, and where OCaml lets you use mutable states.

It doesn’t mean there are no guardrails, however, and OCaml’s type system does a lot of the heavy lifting. It catches type mismatches at compile time, makes illegal states unrepresentable through sum types, and distinguishes between values that are and aren’t mutable. Essentially, it catches a lot of bugs at compile time. But most values in OCaml are immutable by default and users have to purposefully opt-in to mutability with ref mutable record fields, or arrays. So the language nudges you towards pure functions and immutability without strictly enforcing it. Furthermore, the OCaml 5 release introduced effect handlers, which extends the language’s ability to describe and manage side effects.

Hopefully, this gives you a sense of how nuanced the answer to ‘what is functional programming?’ actually is, and just how many languages adopt some but not all of its features, and what those compromises can look like. Further examples of popular multi-paradigm languages include Java, Python, JavaScript, TypeScript, and Common Lisp (interestingly, its ‘cousin’ Clojure is functional in style, providing immutable data structures and lazy evaluation).

However, this doesn’t answer the question of ‘why do we use FP?’ which is what we’ll look at next.

Key Features and Benefits of Functional Programming Principles

Software engineers choose FP because they value the benefits of the way it structures code. Some of the most well-known ones are:

Readability & Easier Reasoning:

Pure functions, which avoid side effects, allow developers to reason about programs more easily by simply knowing the inputs and outputs. There are no external states affecting the functions’ behaviour, which makes code more predictable. Furthermore, the ‘function-first’ philosophy (think first-class functions and higher-order functions) of FP encourages the use of ‘function composition’, where small functions are chained together to express complex behaviour. This modular style makes code easier to understand, maintain, and more reusable.

Example:

let twice f x = f (f x)  (* higher-order function *)
let add_three = twice (+) 1  (* function composition: (+) 1 ∘ (+) 1 *)

FP languages also have features like pattern matching and type inference that contribute to the overall readability of code.

Example:

type shape = 
  | Circle of float 
  | Rectangle of float * float

let area = function
  | Circle r -> Float.pi *. r *. r
  | Rectangle (w, h) -> w *. h

Concurrency & Parallelism:

Thanks to pure functions and immutability, concurrent code is easier to reason about. Fewer use cases warrant using locks to avoid data races, which makes concurrency safer and prevents bugs. Similarly, parallel programming is also safer as no mutable states means that multiple threads can call the same function, and immutable values can be shared without the need to synchronise, which means no deadlocks resulting from threads waiting on each other.

Another, more advanced, concept that helps is lazy evaluation, which reduces the number of times an expression needs to be evaluated and makes parallel code more efficient.

Fewer Bugs:

Our old friends immutability and pure functions make entire categories of bugs, including shared state corruption, action-at-a-distance, and order-of-execution issues, structurally impossible. For FP languages where referential transparency applies, caching/memoisation bugs are not an issue. For languages with strong type systems like Haskell and OCaml, bugs like null/undefined errors, unhandled cases, and incorrect function composition cannot occur.

It is also worth mentioning that code that is easier to read and reason about tends to make debugging easier and help developers discover and fix more bugs during development.

Example:

(* Referentially transparent - pure function *)
let square x = x *. x

(* Not referentially transparent - depends on external state *)
let () = 
  let counter = ref 0 in
  fun x -> incr counter; x + !counter

Testability:

Code that doesn’t have side effects is significantly easier to test, and functional programming languages with pure functions and immutability are especially well-suited to property-based testing (PBT). PBT tests verify that certain properties of a program hold true for a wide range of inputs. Unit tests, which test individual functions’ behaviour, also benefit from these FP principles since pure functions are easy to test in isolation as they don’t rely on external or mutable data.

There are multiple property-based tests you can try for OCaml code, including Quickcheck-inspired PBTs, and several PBT libraries used to test multicore OCaml.

Simplifying testing is similar to making debugging easier; it promotes a test-based development environment and, by doing so, improves the overall quality of the code.

Example:

open QCheck

let test_reverse = 
  test property "reverse involution" 
    [int list] 
    ~f:(fun lst -> List.rev (List.rev lst) = lst)

Before You Go All In: Considerations and Limitations

Of course, the functional programming style is not without its trade-offs, or everyone would be using it for everything! A few things to consider are:

The Learning Curve

Some developers find FP principles intimidating when they first come across them, as the design logic is different to imperative and object-oriented programming styles. For example, the abstractions and structures that exist to manage the core features of functional programming (monads, currying, higher-order functions) require a specific understanding of, and ability to visualise, FP concepts. Recursion with functions is another well-known bugbear for people who are used to loops.

However, it is worth noting that as more universities include FP as part of their courses and as more mainstream languages adopt FP features, the number of new programmers who will never have encountered the FP style is shrinking and the learning curve becoming less steep. Furthermore, once you learn how to write code in a side-effect-free, FP, style, you may well find that the time investment was worth it for the benefits that it unlocks.

There are many resources available to learn functional programming (including our own list of resources to learn OCaml), and checking out the ‘learn’ section on a language’s homepage is a good place to start.

Memory & Performance

Since FP uses immutable values, we don’t get new values by changing old ones and instead create new ones when necessary. Constantly creating new values comes with higher memory costs, and in large programs and data structures, this can have noticeable performance implications. However, many languages have structures that mitigate the slowdown. For example, OCaml’s runtime uses structural sharing for persistent data structures. When an immutable list or tree updates, its new version will share as much memory with the old version as possible. Only the changes are ‘new’.

Example:

let original = [1; 2; 3]        (* [1; 2; 3] *)
let new_list = 0 :: original    (* [0; 1; 2; 3] - shares tail [1; 2; 3] *)
(* Only Cons cell for 0 is newly allocated *)

This approach makes immutable values significantly cheaper from a performance perspective.

I/O

I/O with pure functions requires more legwork than when side effects are allowed. Haskell, for example, manages I/O through abstractions like monads. This, while conceptually clean if you understand the theory, can feel intimidating if you don’t, and will look more verbose than in a language that allows side effects, like Python. OCaml uses libraries like Lwt, Async, and Eio to provide the abstractions needed for concurrent I/O, which keeps side effects organised and composable. This is a slightly more lightweight approach to I/O with pure functions, which OCaml doesn’t strictly enforce but encourages.

Many languages and ecosystems have strategies to mitigate the trade-offs of functional programming. Before deciding on an FP-style language to pursue, it’s worth weighing up its limitations and how it adapts to them.

Who Even Uses Functional Programming?

A common misconception about functional programming is that it’s only used in academic settings or for code that no one really uses.

Yet, HubSpot uses Haskell for their data synchronisation engine to "deliver highly configurable two-way sync to our customers”; Scala is used by several household name brands like Netflix, LinkedIn, and Apple for big data analytics and backend services; and Erlang is used to manage the Nintendo Switch’s messaging system handling millions of concurrent connections.

For OCaml, maybe the most well-known example is Jane Street, the global trading firm. Jane Street uses OCaml as its primary development platform, which includes its massive trading infrastructure. This involves matching engines, execution systems, risk models, and everything else they need to process billions of dollars in trades.

As we have shown, the functional style of programming is eminently useful in real-world applications. If you’re curious to learn more about how its strengths are being applied, check out our post on OCaml and cybersecurity.

The potential trade-offs are worth considering, but the payoff tends to be code you can trust, and as mainstream languages continue to absorb FP ideas, understanding how to leverage those principles is becoming more of an essential part of any developer’s toolkit.

If you're curious about where to go next, OCaml is a great place to start exploring: it gives you the benefits of functional programming without demanding that you master monads on day one. Check out the docs on OCaml.org and our blog post on learning OCaml to get started.

Open-Source Development

Tarides champions open-source development. We create and maintain key features of the OCaml language in collaboration with the OCaml community. To learn more about how you can support our open-source work, discover our page on GitHub.

Explore Commercial Opportunities

We are always happy to discuss commercial opportunities around OCaml. We provide core services, including training, tailor-made tools, and secure solutions. Tarides can help your teams realise their vision