8 Years of Clojure

jkxyz1 pts0 comments

8 years of Clojure | Josh Kingsley

8 years of Clojure

15 May, 2026

It was ClojureScript that first got me into Clojure, when a friend showed me his project using Figwheel and Reagent over coffee in Borough Market. This was back in the day when hot reloading didn't really exist for JavaScript, and React was still using class components. Being able to modify simple functions returning HTML as data (no JSX!), and have the UI update without resetting its state, blew my mind. It still does!

I became enamored with the REPL-driven workflow, the functional-first pragmatism, and the simplicity and depth of the ideas that went into the language's design. For the better part of my career now, I've been lucky enough to get paid to build software using my favorite programming language. I've used it almost exclusively for all of my professional and personal projects.

After 8 years of using Clojure in anger, I've grown in both expertise and appreciation for it. As a niche language, it's helped me to find stable work I enjoy, within teams who share my beliefs about what matters in the craft of software. It's only continued to reward me as I've learned it more deeply. Countless times, it's given me enormous leverage to deliver quickly on large, complex, full-stack features, and to onboard confidently to new codebases with radically different architectural paradigms. I'd consider myself an expert-level Clojure user.

But I've also accumulated some criticisms about Clojure "as she is wrote." Every programming language has its tradeoffs, and I want to share the pain points that would affect my choice of whether or not to use Clojure for future projects. This post is part critique, part personal reflection for me on what's next.

ClojureScript<br>In 2016, when my friend showed me his project, ClojureScript had far and away the best tooling for frontend web development. It really was magical saving a file, seeing the little spinning Clojure logo in the corner of the webpage, and having it update in place. Figwheel set the standard for rapid frontend iteration and inspired the changes in the JavaScript ecosystem that were to come.

Nowadays, hot module replacement is table stakes and works without configuration. ES modules have made that simpler, and they've also simplified publishing libraries and brought JS/TS the kind of tree-shaking optimizations that the Google Closure compiler brought to ClojureScript.

I no longer feel so enthusiastic about choosing ClojureScript, in part because the tooling and the language have not caught up. Something like Vite is much faster, and the Google Closure compiler and library are showing their age. ClojureScript still feels like you're targeting ES2015. And with the web ecosystem moving as quickly as it does, I've found that a lot of frontend code ends up being interop with native browser APIs and NPM packages. Writing interop code is clumsy: it sucks working with TypeScript-typed APIs without the linting and editor completions. It's possible to write JavaScript modules and import them directly, but the story is not as fluid as you'd like. Unfortunately, this is all inherent to coding for the web in a language which is not a first-class citizen.

ClojureScript can still provide a lot of leverage for teams already using Clojure, especially when sharing code. Reader conditionals make it particularly ergonomic to do so. But the JavaScript ecosystem is really compelling now, especially when building on the cutting edge. The web is still married to JavaScript, for better or worse. I'm focusing on using TypeScript in future projects. And I'm getting a lot of mileage from vanilla Web Components - it's refreshing seeing how much can be done without heavy frameworks.

Impedance mismatch<br>An observation I've made, more integral to the language itself, is that Clojure heavily favors a style of coding which creates impedance mismatch when dealing with other systems. Most well-written Clojure codebases end up with an opinionated core mapping to a more interoperable outer API layer. The data at the edges looks more like JSON, and the interior is modeled as mostly flat, open maps, with namespaced keys:

{:user/id 1<br>:user/name "Alice"<br>:address/city "Toronto"<br>:address/zip "123456"}

The above is conceptually an open set of keys which are related to the same entity, rather than a static shape, like the kind of closed, nested objects you'd write in TypeScript:

{ id: 1, name: "Alice", address: { city: "Toronto", zip: "123456" } }

Clojure is a dynamically typed language, and namespaced keywords are a big part of how it makes complex data manageable in a large codebase. A namespaced keyword in a map, like a namespaced variable, has a global meaning (which can be defined using clojure.spec). In Clojure, you don't define a user parameter to a function as a static shape: you define the "herd" of keys that your function needs to do its job.

If you're lucky enough to be using a Clojure-native database like...

clojure using language clojurescript like javascript

Related Articles