Experiment: Making TypeScript Immutable-by-Default
21 comments
·November 18, 2025drob518
bastawhiz
I don't think the benefits of immutability haven't been discovered in js. Immutable.js has existed for over a decade, and JavaScript itself has built in immutability features (seal, freeze). This is an effort to make vanilla Typescript have default immutable properties at compile time.
iLemming
Also, interestingly Clojurescript compiler in many cases emits safer js code despite being dynamically typed. Typescript removes all the type info from emmitted js, while Clojure retains strong typing guarantees.
hden
Mutability is overrated.
jbreckmckye
> If you figure out how to do this completely, please contact me—I must know!
I think you want to use a TypeScript compiler extension / ts-patch
This is a bit difficult as it's not very well documented, but take a look at the examples in https://github.com/nonara/ts-patch
Essentially, you add a preprocessing stage to the compiler that can either enforce rules or alter the code
It could quietly transform all object like types into having read-only semantics. This would then make any mutation error out, with a message like you were attempting to violate field properties.
You would need to decide what to do about Proxies though. Maybe you just tolerate that as an escape hatch (like eval or calling plain JS)
Could be a fun project!
Cthulhu_
One "solution" is to use Object.freeze(), although I think this just makes any mutations fail silently, whereas the objective with this is to make it explicit and a type error.
phplovesong
Sounds easier to just use some other compile to js languge, its not like there are no other options out there.
k__
I'm still mad about Reason/ReScript for fumbling the bag here.
petejodo
Agreed. Gleam is a great one that targets JavaScript and outputs easy to read code
tyleo
This is tangential but one thing that bothers me about C# is that you can declare a `readonly struct` but not a `readonly class`. You can also declare an `in` param to specify a passed-in `struct` can’t be mutated but again there’s nothing for `class`.
It may be beside the point. In my experience, the best developers in corporate environments care about things like this but for the masses it’s mutable code and global state all the way down. Delivering features quickly with poor practices is often easier to reward than late but robust projects.
bilekas
I'm not sure if it's what you mean, but can't you have all your properties without a setter, and only init them inside the constructor for example ?
Would your 'readonly' annotation dictate that at compile time ?
eg
class Test {
private readonly string _testString {get;}
public Test(string tstSrting)
=> _testString = tstSrting ;
}We may be going off topic though. As I understand objects in typescript/js are explicitly mutable as expected to be via the interpertor. But will try and play with it.
voidUpdate
How do immutable variables work with something like a for loop?
bastawhiz
Since sibling comments have pointed out the various ES5 methods and ES6 for-of loops, I'll note two things:
1. This isn't an effort to make all variables `const`. It's an effort to make all objects immutable. You can still reassign any variable, just not mutate objects on the heap (by default)
2. Recursion still works ;)
tgv
Unless you need the index, you can write: for (const x of iterable) { ... } or for (const attribute in keyValueMap) { ... }. However, loops often change state, so it's probably not the way to go if you can't change any variable.
Cthulhu_
If you need the index, you can use .keys() or .entries() on the iterable, e.g.
for (const [index, value] of ["a", "b", "c", "d", "e"].entries()) {
console.log(index, value);
}
Or forEach, or map. Basically, use a higher level language. The traditional for loop tells an interpreter "how" to do things, but unless you need the low level performance, it's better to tell it "what", that is, use more functional programming constructs. This is also the way to go for immutable variables, generally speaking.tantalor
Is TFA (or anyone else for that matter) actually concerned with "immutable variables"?
e.g., `let i = 0; i++;`
They seem to be only worried about modifying objects, not reassignment of variables.
Cthulhu_
That's probably because reassignment is already covered by using `const`.
Of course, it doesn't help that the immutable modifier for Swift is `let`. But also, in Swift, if you assign a list via `let`, the list is also immutable.
voidUpdate
Looks like Rust is https://doc.rust-lang.org/stable/std/keyword.let.html
macintux
Erlang doesn't allow variable reassignment. Elixir apparently does, but I've never played with it.
ruined
typescript handles that well already
foxygen
You use something else like map/filter/reduce or recursion.
It’s interesting to watch other languages discover the benefits of immutability. Once you’ve worked in an environment where it’s the norm, it’s difficult to move back. I’d note that Clojure delivered default immutability in 2009 and it’s one of the keys to its programming model.