This critique provides a necessary reality check by exposing how Clojure’s flexibility often translates into significant cognitive overhead and maintenance challenges in large-scale systems. It is a masterclass in intellectual honesty that prioritizes engineering pragmatism over functional programming dogma.
Deep Dive
Prerequisite Knowledge
- No data available.
Where to go next
- No data available.
Deep Dive
Everything I DISLIKE about ClojureAdded:
All right, everybody. You probably know that Closure is my primary programming language. It's what I make pretty much all of my videos in and also the language that I use for pretty much all of my personal projects. But today, in the interest of fairness, I'm going to be telling you some things that I dislike about the language, just to give you guys a complete and balanced perspective.
In programming, laziness is the idea that code is not evaluated until the moment that it is actually used. In closure, most of the standard tools for working with lists are lazy operations, which means that the computation does not take place until you actually try to evaluate something inside of the resulting list.
Now, I think it was designed this way because it saves processing power on operations that you may or may not need.
But even more importantly, it lets you do operations that might be undefined if the sequence is fully evaluated. This enables certain kinds of programming where you can represent infinite sequences and then do operations on those sequences in a way that's much more concise than you might otherwise be able to. In the same way, uh it can also be really useful in building these kind of multi-step data pipelines. But these days, a lot of the benefits have sort of been subsumed by transducers. Anyway, the problem with lazy evaluation is that it makes it really hard to track down what your program is doing and when side effects occur. Even if you're running pure functions on immutable data structures, it's really hard to track down the performance of various bits of your application because if there are any lazy computations anywhere in your call stack, it sort of masks where the bottlenecks are occurring and it makes it really hard to figure out where processing power is actually being used.
In practice, I wound up using dual blocks everywhere in my program to force eager evaluation, but I wish that it just like didn't work this way. Laziness is sometimes useful, but you have to be so careful around it that I would much rather it be that you have to explicitly declare every time you want to use laziness rather than just having it on by default.
On the topic of explicit declarations, I often get frustrated by Closure's visibility rules. By default, all of Closure's default definition statements, including defaf, defn, and def macro, are all fully public, which means that if you define a new function, you can now call it from anywhere in your codebase. And so this is great for quick scripting and data science tasks, but once you start building bigger projects, it starts to get more and more difficult to manage. Now you can make private variables in closure. It's actually not that difficult. But the problem is that I'm lazy and when I'm programming, I don't want to type more characters unless I actually need to do it. It's a little silly, but I think it's one of these cases where making doing things the right way, the easy way, in practice makes for much more well-regimented and well organized projects. I actually really like the Java approach here where everything is package private and then if you want anything more or anything less than that, you have to explicitly declare it because it gets out of the way when you're working on tiny little applications. But it prevents things from dripping out into a mess if you start to work on very large projects.
When I was working at Google, I actually really enjoyed the Basil build system.
And there are tools like Polyith that can bring a similar feeling to your closure projects. Overall though, when you declare something as public in a very large project, you're really making a very big statement about what that is and what it does. And I would prefer it if the language caused you to make that statement a little bit more intentionally.
This has been a complaint of mine for a really long time and I even made a whole video about it. But the TLDDR is that in my personal experience, I think that the tool chain for refactoring closure projects is less complete and reliable than similar tools in the Java ecosystem. And I find that even when refactoring tools work most of the time, unless they work 100% of the time or near 100% of the time, I still finding myself needing to read or grip through my code to verify that the refactoring operation was done correctly. And then that sort of defeats the time savings of using automated refactoring tools in the first place. I'm grateful for the tools that we do have, but you know, it's a smaller ecosystem and there are a number of features that make automated refactoring more challenging like custom multimethod dispatch, unnamed spaced keywords, especially on record definitions and of course the absence of a static type system. I've been thinking about the way to manage some of these concerns and you know this fixes some of the problems but I think the challenges are much deeper than that and it really has to do deep down with a lack of types. As you probably know closure is a dynamically typed language which in practice means that it once again flows very quickly for rapid development but can be a bit unwieldly especially when trying to reorganize larger projects. As a result, a number of libraries have popped up to do type validation for closure. And while I find that these libraries are really useful in my own projects, I often struggle to make sure that I'm actually verifying everything that I want to verify. And I also need the behavior to be exercised either by a unit test or somewhere during development in order for the error to actually be caught. On the other end of the spectrum, there's another library called typed closure, which adds optional typing to your closure projects. So, it sort of retrofits a static type checker onto your existing closure codebase, but it's a tremendous amount of work to kind of uh get set up and get meaningful results out of it.
And so, kind of word on the street is these days not that many people are using it. I was actually an early user of type closure uh way back in 2016. Uh, I was super excited about the project, but I ended up giving up on it because it was just a little bit too much overhead and a little bit too much kind of mental effort uh to use and maintain.
I actually started working on my own project to add a gradual type llinter onto closure, but it's still in the works and I'm working hard at it. Uh, so uh, coming soon. I know I'm talking a lot about projects that I've worked on, but I can assure you that the dependency goes the other way around in that I am not bringing up these pain points because they are related to my projects.
It's that I started working on these projects in response to some of the biggest problems that I had. Last up, I want to talk about default patterns.
Closure is a lisp, and lisps are a creative family of languages used by a lot of creative-minded people. As a result, there are a lot of different ways to do things in Closure. And I've heard an engineering manager describe the feeling of walking into different code bases and seeing how different companies all kind of had their own flavor and style. And you know, many people have different opinions about this, but you know, I'm of the camp that I think a little bit of creativity and joy in programming uh can be a good thing. But sometimes I wish that there were more brain deadad solutions to some of the most common types of problems. So for example in Python if you're processing some request and you have a number of conditions that are kind of like failure modes where you either want to return none or redirect to somewhere else. There's kind of this pattern of early return statements where you put return statements guarded by if clauses as early in the function definition as they can possibly go. In closure, it's a little bit trickier because there isn't the same notion of early return. But there also isn't a replacement approach that feels brain dead and obvious to work with. In practice, most people are using con to achieve this type of pattern. And there's even a macro library that can help you organize these things a bit better. But what stands out to me is that the Python approach is just so brain dead. You can write it in closure and in some cases it's maybe even a little bit more elegant in closure, but I kind of wish that closure had more ways to keep the easy things easy and provide sort of more brain dead approaches to common problems just to kind of reduce the amount of mental energy that you need to use. Another example is Python's decorator syntax for memo operations. If you want to memoize a function in Python, it's really obvious how to do it. And there just kind of isn't anything to really think about. In closure, you can very easily achieve the same thing by calling memoise. But it's sort of unclear whether to define a function first and then memoize it or do memoization inside of the function body. I usually favor the first approach because it's kind of nice to be able to call the uncashed function from the reple if I need to, but it's that tiny little bit of mental effort that I have to use every time that I want to call memoise to kind of figure out which approach to use and I wish we had better kind of like crispier standards and organizational tools to kind of reduce that mental load. Despite all these problems, Closure is and remains a fantastic language to this day. One of the biggest features of Closure is its stability. For those of you who haven't seen it before, this is a comparison between Closure and Scala of how the two core language code bases have changed over time. Closure on the top has kept most of the same code throughout its entire life cycle with more recent updates being relatively minor and containing very few deletions whereas Scola seems to rewrite itself every few years. What's going on here is that the core team is very intentional about avoiding change and definitely doesn't want to overwrite all of the defaults midway through the languages history in a way that might be incompatible with existing code bases.
This is in contrast with the entire JavaScript ecosystem that seems to ship chaos once every 2 or 3 years. And importantly, the flexibility and power of lisp lets you build the extensions that you want around the language so that you can often make these kinds of improvements without siloing them from each other. Anyway, uh if you enjoyed the video, give it a like, subscribe to the channel, and I'll see you in the next one.
Related Videos
Agentforce NOW AMA: Build with React and Salesforce Multi-Framework
SalesforceDevs
490 views•2026-05-28
How agent o11y differs from traditional o11y — Phil Hetzel, Braintrust
aiDotEngineer
450 views•2026-05-28
WEB TECHNOLOGIES UNIT-2 | Degree 4th sem BCOM Computers web technologies unit-2 full explanation💯✅
LearnwithSahera
1K views•2026-05-29
More tests are always better? How to use AI to identify tests that bring little value
Alliance4Qualification
335 views•2026-05-29
Search Algorithms Explained in 60 Seconds! 🤖💨
samarthtuliofficial
218 views•2026-06-01
People of Game of Thrones using JavaScript DOM
AltCampus
296 views•2026-05-30
Introduction to Problem Solving Part - 1 | Lecture 1 | Intermediate DSA
ascensionix
107 views•2026-05-29
🚀 BCS613C Compiler Design | Module 1 to 5 Schema Evaluation 🔥 | VTU 6th Sem 💯 #VTU #bcs613c #exam
Pranavaa-y4y
104 views•2026-06-02











