During his talk at Clojure South, Alex Miller, Principal Software Engineer at Nubank, addressed a problem that lies at the heart of every developer’s daily work: the painful ways we often handle data. Our industry is rife with fragile formats, objects laden with boilerplate code like getters and setters, and the ever-present complexities of mutable state that demand layer upon layer of protection. This friction isn’t just a technical debt; it’s a tax on our creativity and our joy.

Clojure offers a powerful alternative to this paradigm. It places simple, immutable data and pure functions at the very core of its philosophy. It’s a fundamentally different way of thinking about software rather than just a different syntax. By embracing this model, we can build cleaner, more mathematical, and incredibly powerful mental models. This approach, as Alex demonstrated, leads to systems that are not only more robust but also more joyful to create.

The journey to this simpler, more powerful way of programming begins by rethinking a word we use every day: “value.”

Back to basics: what truly is a “value”?

While programming involves manipulating various entities, Clojure’s power stems from a rigorous definition of ‘values.’ This distinction is far from a semantic triviality; it is the core design principle that gives the language its characteristic elegance. In this context, a value represents a specific category of data, defined by immutable properties that change how we reason about state.

According to Alex, for something to be considered a true value, it must possess four key characteristics:

  • Immutable: A value cannot be changed. The number 100 doesn’t magically become 50. Once created, it is constant for all time.
  • Comparable: You can take two values and determine if they are the same. This property allows for sorting, indexing, and reasoning about equality in a straightforward way.
  • Sharable: A value can be passed to another thread, sent over a network, or written to disk and read back without fear of it being altered. Its integrity is guaranteed.
  • Precise: To be sharable and comparable, a value must have a precise, concrete representation—ultimately, as a specific sequence of bits.

This definition clarifies what is not a value. Things like network sockets, file handles, streams, and mutable objects fail this test. 

“Those things are not values… You cannot compare them or write them down precisely.”

Alex Miller, Principal Software Engineer at Nubank

They are processes or resources, not facts. A subtle but important distinction exists here: a file path can be treated as a value, as it is an immutable, precise description. A file handle, however, representing a live connection to a changing resource, cannot.

Embracing immutability for all values, including collections, has profound consequences that simplify system design immensely:

  • No locks needed for reading: Since a value can never change, you can read it from any number of threads without locks or synchronization.
  • Fearless sharing: You can freely pass data structures between concurrent processes or distributed systems, confident that you are sharing information, not a potential source of bugs.
  • Effortless caching and history: Caching becomes trivial when the data never changes. It also enables powerful features like undo stacks or “time-travel” debugging, as you can simply hold onto references to previous states.

This design is not without its costs; it is a conscious engineering choice made for immense benefits. 

“These are awesome properties. They’re not free… but immutability is definitely worth the price that you pay for it.”

Alex Miller, Principal Software Engineer at Nubank

This powerful philosophy isn’t just for single numbers or strings; in Clojure, it extends to the very collections that structure our data.

The bedrock: simplicity in four core collections

If Clojure South had a silent protagonist, it was the humble map. This is no accident. Clojure deliberately rejects a sprawling ecosystem of collection types in favor of a small, powerful bedrock of four core structures. This strategic choice to favor a minimal set of data structures fosters immense reusability and clarity.

Clojure is built on four primary collection types, each serving a distinct structural purpose:

  • Lists: Sequential collections where values are efficiently added to the front.
  • Vectors: Indexed, sequential collections where you can quickly access any element by its position.
  • Maps: Unordered collections of key-value associations.
  • Sets: Unordered collections of unique values.

What makes these collections so powerful is not just that they are immutable, but that they are all accessible through a single, universal vocabulary of functions. In many object-oriented languages, “every class invents a new language.” To get a user’s name, you call user.getFirstName(); for an order’s total, order.getTotal(). This is a major source of friction; developers must constantly context-switch, learning a new mini-protocol for every object they encounter.

Clojure rejects this bespoke complexity. Instead, it provides a unified set of functions—get, keys, vals, seq, count—that work on all data. This common vocabulary dramatically reduces cognitive overhead. As Alex Miller noted, once you 

“Learn how to use map, filter, remove and you know how to navigate your entire data set.” 

Alex Miller, Principal Software Engineer at Nubank

The same tools work everywhere, making data feel close, transparent, and easy to reshape.

This common language for manipulating data is the key that unlocks Clojure’s central, and remarkably simple, algorithm for solving problems.

The core algorithm for programming

At the very heart of the talk, Alex presented the key slide—a simple, two-step algorithm that encapsulates the entire Clojure development philosophy. It is an idea so powerful that it can fundamentally reshape how one approaches building software.

“There is a very simple algorithm for programming in Clojure: represent your domain as data and write functions that transform that data. That’s it.” Alex Miller, Principal Software Engineer at Nubank

This two-step model of “data in, data out” is profoundly effective for several reasons:

  • Predictable & Testable: Pure functions that operate on immutable values are the bedrock of predictability. Given the same input, they will always produce the same output, making them trivial to reason about, test in isolation, and debug.
  • Composable: Small, pure functions are like Lego bricks. They can be combined and composed into larger, more complex functions that retain the same desirable properties of predictability and testability.
  • Safe for Concurrency: By eliminating shared, mutable state, this model sidesteps an entire class of notoriously difficult concurrency bugs. There are no race conditions or deadlocks when functions are simply transforming immutable data.
  • Stable & Evolvable: Systems built this way are remarkably stable and easy to evolve. Adding a new feature often just means adding a new key to a map. Existing functions that don’t know about the new key will continue to work, completely unaffected. This allows systems to grow by addition rather than by modification, reducing the risk of breaking existing code.

    This might sound elegant in theory, but its true power becomes clear when applied to real-world problems.

    From to-do lists to music: data in action

    To demonstrate the core algorithm in practice, Alex walked through a series of diverse examples, showing how any domain can be modeled as data and manipulated with functions.

    A simple to-do list

    A to-do item is not an object with methods; it is simply a map containing keys like :task, :errand, and :duration. Miller’s advice here is a cornerstone of pragmatic Clojure design: 

    “Always have a bottom data layer that bottoms out and verbose maps you’ll thank me later.”

    Alex Miller, Principal Software Engineer at Nubank

    From this foundation, a common pattern emerges: write a small function to handle a single item, then another to handle the collection. The task of transforming to-do items into HTML follows this perfectly, with one function converting a single map into a Hiccup data structure, and a second mapping that function over a collection. This emphasis on small, single-purpose functions is pervasive. As Miller observed, “it’s not uncommon to look at a closure codebase and see that… the average size of a function is five lines.”

    Conway’s game of life

    The infinite grid of Conway’s Game of Life is elegantly represented not with a massive, two-dimensional array of zeros and ones, but with a sparse representation: a simple set containing the [x, y] coordinates of “live” cells. This data modeling choice is incredibly powerful. The entire game logic becomes a pipeline of pure functions that takes one set of coordinates as input and produces the next set as output, implementing the game’s rules with mathematical clarity.

    Graphics and music as data

    The pattern extends even to creative domains. A circle on a screen can be represented as a map with :x, :y, :radius, and :color keys. Similarly, a musical note can be a map with :pitch, :octave, and :duration. By modeling these entities as data, Alex Miller was able to build a mini-synth on stage, using a sequence of maps to play the iconic five-note theme from “Close Encounters of the Third Kind.”

    In every case, the solution was found not by designing complex class hierarchies, but by first asking,  “What is the simplest data structure that can represent this problem?”, and then applying simple, composable functions to transform it.

    While this model covers the vast majority of programming tasks, Clojure also provides a principled approach for the rare moments when values truly need to change over time.

    Managing change: a principled approach to state

    Inevitably, some parts of an application must change. A user’s session, a database connection, a UI component’s state—these all require managing change over time. For these scenarios, Clojure provides a clear answer that avoids the chaos of unmanaged mutability. The goal is to create a “stable logical identity that’s going to have different immutable values over time.”

    Clojure offers a small set of stateful constructs for this purpose, primarily atoms, refs, and agents. Instead of directly mutating the value they hold, you provide them with a pure function that computes the next state from the current one. This design means developers don’t perform manual locking; reads are always available without coordination, and writes are managed safely by the Clojure runtime.

    What’s most telling is how this plays out in practice. When Clojure was created, its Software Transactional Memory (STM) system, built around refs, was seen as a flagship feature. Yet, over years of production use, a surprising truth emerged. As Miller noted, developers discovered that they “can get by with an amazingly little amount of state in your system.” The disciplined, data-first approach naturally leads to architectures where the vast majority of the code is pure and stateless, reinforcing the core philosophy of the language.

    Conclusion: building on rock, not on sand

    The core message of Alex’s talk is a call for a return to simplicity. The philosophy is clear: build systems on a foundation of immutable values and pure functions, and manage state only when absolutely necessary. This approach yields programs that are easier to understand, grow, and maintain.

    Miller concluded with a powerful metaphor that perfectly captures the difference between this approach and more conventional, mutable-state-heavy paradigms. Following this philosophy is like “building on bedrock instead of building on sand.”

    The stability and clarity that come from this foundation are not just technical achievements; they are the source of genuine professional joy. It allows developers to focus on solving problems rather than fighting with the accidental complexity of their tools. The standing ovation Alex received was not just for a well-delivered talk, but for articulating a philosophy that brings the joy back into the craft of programming.

    Check our job opportunities