Elm as target language for vibe coding

Having been a fan of functional programming and following the breadcrumbs of reactive systems, it's not surprising that I'm a fan of Elm, and had experience building different systems in it. It's too bad that enthusiasm for Elm didn't last until vibe coding came on the scene, because it seems like Elm as a language would be a great fit for vibe coding.
What are the aspects of a language that make for a good language for LLMs to code in? In a lot of ways, it's the same things affordances that make it better for a human to code in. But there's some specific pros that Elm has going for it.
Strong type system
LLMs have a tendency to hallucinate, and hence make mistakes when writing code. These mistakes can result in code that just don't work or don't run. Usually what we do is to blow the changes away and try and give the coding agent more context as to what we want.
But another method is to provide tools to the coding agents to self-correct, such as build-scripts, linters, and type-checkers. When you have a language that runs without error if it compiles, then it doesn't require a lot of hand-holding. Hence, you can just let the coding agent run until it finishes before checking its work, rather than having to keep going back to it after each small step.
I've found that to get vibe-coding to work for you, you have to be ok with just letting go and not micromanage every little step that the coding agent is doing. While you can micromanage its design and architectural choices, you don't want to be doing that for basic syntax.
Exceptionally clear error messages
Elm was one of the first languages to prioritize clear error messages. Clear error messages not only help humans, but also LLMs. By having clear error messages, it's easier for the coding agent to find the place in the code that's having the error.
Like strong type checking, this helps the coding agent be more independent on the low level details on the code. Hence it can run independently for as long as it can without intervention from the developer on things that it can fix itself. This frees up the developer to focus on things that coding agents are currently bad at, such as design choices and architecture.
Single file by convention to help context caching
Elm programs, by convention tend to congregate into a single large file. There's not really a reason or culture to break them up because most of the methods are relatively short (besides the routing switch), and it's easier to jump between different methods if it's all in one file.
Turns out this is also helpful for LLMs, because they can leverage prompt caching. Often times, we're using the same context for LLMs, so instead of having them process the same tokens again and again, we can use caching to help lower the cost of generating a response.
Strong mental model to help humans course correct
Elm programs are famously regular with The Elm Architecture (TEA) when building front-end web apps. It also has a strong mental model for human developers to follow. When you look at an Elm program, it's almost boring in how regular it is.
This is helpful, because for any code that the coding agent generates, it's fairly easy to follow what it's doing. It's also easy to course correct the agent, should anything go wrong.
Why I couldn't choose it
Despite all these positives, I couldn't choose Elm in 2025 for vibe coding. All of the reasons are narrowly and uniquely specific to the app that I'm building. But I will mention one specific weakness of Elm programs: it forces you to break up the logical trace of your program along side-effects.
A single conceptual trace
Elm is great when a single message matches up with a single conceptual action that a user can take. So given any single event that a user can generate, it's easy to look at the state changes that a single user event will generate. These tend to be typical CRUD events in a basic web app.
However, there are instances where a single conceptual action needs to be spread across many different messages in the system. This tends to happen when a single user event invokes sides-effects to other parts of the system through ports. It's harder to trace control and data flow of the program when you have to flip back and forth between the internal and the external part of the system this way.
I think this is the largest weakness of Elm, because it effectively forces you to draw boundaries of your program when it has to use any side-effects, instead of around semantic and conceptual boundaries of an entire job or action that the user needs to do.
This is where I think Algebraic Effects would be great for Elm programs. Algebraic Effects are often explained as "resumable exceptions", but in reality, they're a new kind of control flow. Imagine that every time you need a result from a side-effect, instead of passing a Cmd
all the way back up to the top of the call stack, you can "throw" the Cmd
, get a result from a handler, and just resume from where you were. That way, you can write a more direct style program and your program remains purely functional.
I left a lot of explanation on the table, since my job here isn't to explain algebraic effects, but just to point out where it's lacking in 2025 with respect to the reality that vibe-coding will be in the future of all software engineering disciplines in one form or another. Elm probably won't be the poster child taking up the mantle for a vibe-code-friendly-LLM-affordance language, but it has all the makings of one. And hopefully, some programming language designer out there will take notice.