Top
Best
New

Posted by azhenley 10/31/2025

John Carmack on mutable variables(twitter.com)
515 points | 627 commentspage 4
thefaux 10/31/2025|
Even better is to use tail calls instead of loops and eliminate mutable variables entirely.
rezonant 10/31/2025||
https://xcancel.com/id_aa_carmack/status/1983593511703474196
seattle_spring 10/31/2025|
CMV: HN should just automatically replace x links with xcancel
warmwaffles 10/31/2025||
This shouldn't be a hard and fast rule for everything. Be treated as guidelines and allow the programmer some wiggle room to reuse variables in situations that make sense.
AnotherGoodName 10/31/2025|
Going too hard on mutation means you usually end up with larger structures that are recreated completely. Those themselves are then the point of mutation. This can be helpful if the larger object needs a lot of validation and internal cross rules (eg. You can't set 'A' if 'B' is true, you can validate that when recreating the larger object whereas if 'A' was mutable on it's own someone might set it and cause the issue much later which will be a pain to track down).

Anyway the outcome of trying to avoid mutation means instead of simply setting player.score you get something like player = new Player(oldPlayerState, updates). This is of course slow as hell. You're recreating the entire player object to update a single variable. While it does technically only mutate a single object rather than everything individually it's not really beneficial in such a case.

Unless you have an object with a lot of internal rules across each variable (the can't be 'A' if 'B' example above) it's probably wrong to push the mutation up the stack like that. The simple fact is a complex computer program will need to mutate something at some point (it's literally not a turing machine if it can't) so when avoiding mutation you're really just pushing the mutation into a higher level data object. "Avoid mutations of data that has dependencies" is probably the correct rule to apply. Dependencies need to be bundled and this is why it makes sense not to allow 'A' in the above example to be mutated individually but instead force the programmer to update A and B together.

QuadrupleA 10/31/2025||
Love Carmack, but hard disagree on this and a lot of similar functional programming dogma. I find this type of thing very helpful:

    classList = ['highlighted', 'primary']
    if discount:
        classList.append('on-sale')
    classList = ' '.join(classList)
And not having to think about e.g. `const` vs `let` frees up needless cognitive load, which is why I think python (rightly) chose to not make it an option.
ColeShepherd 10/31/2025||
Some potential alternatives to consider:

1.

    classList = ['highlighted', 'primary']
        .concatif(discount, 'on-sale')
        .join(' ')
2.

    classList = ' '.join(['highlighted', 'primary'] + (['on-sale'] if discount else []))
3.

    mut classList = ['highlighted', 'primary']
    if discount:
        classList.append('on-sale')
    classList = ' '.join(classList)

    freeze classList

4.

    def get_class_list(discount):
        mut classList = ['highlighted', 'primary']
        if discount:
            classList.append('on-sale')
        classList = ' '.join(classList)
        return classList

    classList = get_class_list(discount)
salutis 10/31/2025||
Fennel (Lisp):

    (table.concat [:highlighted :primary (if discount :on-sale)] " ")
considerdevs 10/31/2025||
This is the kind of wisdom that comes after hours of debugging and discovering the bug was your own variable reuse.

Wouldn't this be an easy task for SCA tool e.g. Pylint? It has atleast warning against variable redefinition: https://pylint.pycqa.org/en/latest/user_guide/messages/refac...

maleldil 10/31/2025|
This is only for redefinitions that change the type. If you re-assign with the same type, there's no warning. However, pylint does issue warnings for other interesting cases, such as redefining function arguments and variables from an outer scope.
garrison 11/2/2025||
This reminds me of an earlier blog post by the same author: https://web.archive.org/web/20121111234839/http://www.altdev...
noduerme 10/31/2025||
Why loops specifically? Why not conditionals?

A lot of code needs to assemble a result set based on if/then or switch statements. Maybe you could add those in each step of a chain of inline functions, but what if you need to skip some of that logic in certain cases? It's often much more readable to start off with a null result and put your (relatively functional) code inside if/then blocks to clearly show different logic for different cases.

turtletontine 10/31/2025|
There’s no mutating happening here, for example:

  if cond:
      X = “yes”
  else:
      X = “no”
X is only ever assigned once, it’s actually still purely functional. And in Rust or Lisp or other expression languages, you can do stuff like this:

  let X = if cond { “yes” } else { “no” };

That’s a lot nicer than a trinary operator!
nielsbot 10/31/2025|||
Swift does let you declare an immutable variable without assigning a value to it immediately. As long as you assign a value to that variable once and only once on every code path before the variable is read:

    let x: Int
    if cond {
        x = 1
    } else { 
        x = 2
    }

    // read x here
ruszki 10/31/2025|||
Same with Java and final variables, which should be the default as Carmack said. It’s even a compile time error if you miss an assignment on a path.
sambishop 10/31/2025|||
that's oldschool swift. the new hotness would be

  let x = if cond { 1 } else { 2 }
lock1 10/31/2025|||
IMO, both ternary operator form & Rust/Haskell/Zig syntax works pretty well. Both if expression syntax can be easily composed and read left-to-right, unlike Python's `<true-branch> if <cond> else <false-branch>`.
koolba 10/31/2025||
This is the mental distinction of what something represents vs what is its value. The former should never change regardless of mutability (for any sane program…), and the latter would never change for a const declaration.

The value (pun intended) of the latter is that once you’ve arrived at a concrete result, you do not have to think about it again.

You’re not defining a “variable”, you’re naming an intermediate result.

vezcha 11/2/2025||
The efficiency advantage of immutability needs to be made more transparent and understood by engineers. Memory flexibility and recyclability have potential costs and should be minimized as much as possible. More importantly, mutability is often unnecessary.
stevage 10/31/2025|
In JavaScript, I really like const and have adopted this approach. There are some annoying situations where it doesn't work though, to do with scoping. Particularly:

- if (x) { const y = true } else { const y = false } // y doesn't exist after the block - try { const x = foo } catch (e) { } // x doesn't exist after the try block

latexr 10/31/2025||
JavaScript’s `const` has the bigger issue that while things can’t be reassigned, they can still mutate. For example:

  const myArray = [1,2,3]
  myArray.push(4)
  myArray // [1, 2, 3, 4]
stevage 10/31/2025||
In what way is that an issue?
maleldil 10/31/2025||
Because this isn't immutability. The goal is to have a way to define an object that will never change after initialisation, and JS's const isn't it.
stevage 10/31/2025||
Clearly that isn't the goal.
maleldil 11/1/2025||
By "the goal", I mean TFA's, not JS's.
NathanaelRea 10/31/2025|||
You could do an absolutely disgusting IIFE if you need the curly brace spice in your life, instead of a typical JS ternary.

  const y = (() => {
    if (x) {
      return true;
    } else {
      return false;
  })();
cookiengineer 10/31/2025|||
Technically you could just use an assignment ternary expression for this:

    const y = (x === true) ? true : false;
I used this kind of style for argument initialization when I was writing JS code, right at the top of my function bodies, due to ES not being able to specify real nullable default values. (and I'm setting apart why I think undefined as a value is pointless legacy).

    Composite.prototype.SetPosition(x, y, z) {

        x = (isNumber(x) && x >= 0 && x <= 1337) ? x : null;
        y = (isNumber(y) && y >= 0 && y <= 1337) ? y : null;
        z = isNumber(z) ? z : null;

        if x !== null && y !== null && z !== null {
            // use clamped values
        }

    }
NathanaelRea 10/31/2025|||
I typically only use ternaries for single operations and extract to a function if it's too big. Although they are quite fun in JSX. For your code i'd probably do:

  function SetPosition(x, y, z) {
    if (!(isNumber(x) && isNumber(y) && isNumber(z))) {
      // Default vals
      return;
    }
    x = clamp(x, 0, 1337);
    y = clamp(y, 0, 1337);
    z = z;
  }
cookiengineer 10/31/2025||
I always call this the difference of return branch styles. Yours I'd describe as "fast fail" aka return false as quickly as possible (for lack of a better terminology) whereas I personally prefer to have a single return false case at the bottom of my function body, and the other validation errors (e.g. in Go) are usually in the else blocks.

In JS, errors are pretty painful due to try/catch, that's why I would probably these days recommend to use Effect [1] or similar libraries to have a failsafe workflow with error cases.

Errors in general are pretty painful in all languages in my opinion. The only language where I thought "oh this might be nice" was Koka, where it's designed around Effect Types and Handlers [2]

[1] https://effect.website/

[2] https://koka-lang.github.io/koka/doc/index.html

fuzzythinker 10/31/2025|||
nitpick: cleaner w/o ()'s, as '=' is the 2nd lowest operator, after the comma separation operator.
cookiengineer 11/2/2025||
I guess I'm that one guy that likes expression brackets and statement ending symbols.
stevage 10/31/2025|||
I love it.
askmrsinh 10/31/2025|||
Why not do:

const y = x ? true : false;

stevage 10/31/2025|||
I'm talking about cases with additional logic that's too long for a ternary.
1718627440 10/31/2025|||
That sounds like a more complicated way to write

    const y = (bool)x;
or

    const bool y = x;
keeda 10/31/2025||
Ditto. These days those are the only cases where I use "let" in JS. The thing I miss most from Kotlin is the ability to return values from blocks, e.g.

val result = if (condition) { val x = foo() y = bar(x) y + k // return of last expression is return value of block } else { baz() }

Or:

val q = try { a / b } catch (e: ArithmeticException) { println("Division by zero!") 0 // Returns 0 if an exception occurs }

Edit: ugh, can't get the formatting to work /facepalm.

More comments...