from Hacker News

John Carmack on mutable variables

by azhenley on 10/31/25, 2:34 AM with 607 comments

  • by EastLondonCoder on 10/31/25, 9:30 AM

    After a 2 year Clojure stint I find it very hard to explain the clarity that comes with immutability for programmers used to trigger effects with a mutation.

    I think it may be one of those things you have to see in order to understand.

  • by hyperhello on 10/31/25, 3:00 AM

    > I wish it was the default, and mutable was a keyword.

    I wish the IDE would simply provide a small clue, visible but graphically unobtrusive, that it was mutated.

    In fact, I end up wishing this about almost every language feature that passes my mind. For example, I don't need to choose whether I can or can't append to a list; just make it unappendable if you can prove I don't append. I don't care if it's a map, list, set, listOf, array, vector, arrayOf, Array.of(), etc unless it's going to get in my way because I have ten CPU cores and I'll optimize the loop when I need to.

  • by slifin on 10/31/25, 8:12 AM

    Yeah I wish variables were immutable by default and everything was an expression

    Oh well continues day job as a Clojure programmer that is actively threatened by an obnoxious python take over

  • by gwbas1c on 10/31/25, 3:02 AM

    Years ago I did a project where we followed a lot of strict immutability for thread safety reasons. (Immutable objects can be read safely from multiple threads.)

    It made the code easier to read because it was easier to track down what could change and what couldn't. I'm now a huge fan of the concept.

  • by nixpulvis on 10/31/25, 1:49 PM

    I try to keep deeper mutation to where it belongs, but I'll admit to shadowing variables pretty often.

    If I have a `result` and I need to post-process it. I'm generally much happier doing `result = result.process()` rather than having something like `preresult`. Works nicely in cases where you end up moving it into a condition, or commenting it out to test an assumption while developing. If there's an obvious name for the intermediate result, I'll give it that, but I'm not over here naming things `result_without_processing`. You can read the code.

  • by anymouse123456 on 10/31/25, 1:50 PM

    I completely agree with the assertion and the benefits that ensue, but my attention is always snagged by the nomenclature.

    I know there are alternate names available to us, but even in the context of this very conversation (and headline), the thing is being called a "variable."

    What is a "variable" if not something that varies?

  • by lopatin on 10/31/25, 3:07 AM

    How fast this got to the top, you would think John Carmack just invented nuclear fusion.
  • by agentultra on 10/31/25, 12:05 PM

    Agree. After working seriously on a large production Haskell codebase for several years I definitely took it for granted. Now that I’m writing stuff in C again I do think immutability should be the default.

    const isn’t really it though. It could go further.

  • by DashAnimal on 10/31/25, 3:39 AM

  • by sunrunner on 10/31/25, 10:22 AM

    I like the idea of immutable-by-default, and in my own musings on this I've imagined a similar thing except that instead of a mutable keyword you'd have something more akin to Python's with blocks, something like:

        # Immutable by default
        x = 2
        items = [1,2,3]
    
        with mutable(x, items):
            x = 3
            items.append(4)
    
        # And now back to being immutable, these would error
        x = 5
        items.append(6)  
    
    I have put almost zero thought into the practicality of this for implementation, the ergonomics for developers, and whether it would be different enough and useful enough to be worth having.
  • by bitbasher on 10/31/25, 2:37 PM

    > ... making almost every variable const at initialization is good practice. I wish it was the default, and mutable was a keyword.

    Rust mentioned!

  • by y0ned4 on 10/31/25, 9:22 AM

    When I started programming in Haskell, where all variables are immutable, I felt like I was in a straitjacket

    Then, suddenly, the enlightenment

  • by or_am_i on 10/31/25, 9:45 AM

    In JetBrains editors it's possible to highlight mutable variables, at least in the languages where the distinction exists. My go to setting in Kotlin is to underscore all `var`'s, for two reasons:

    - this makes them really stand out, much easier to track the mutation visually,

    - the underscore effect is intrusive just-enough to nudge you to think twice when adding a new `var`.

    Nothing like diving into a >3k lines PR peppered with underscores.

  • by omnicognate on 10/31/25, 9:03 AM

    > In C/C++, making almost every variable const at initialization is good practice. I wish it was the default, and mutable was a keyword.

    I know it's irrelevant to his point, and it's not true of C, and it doesn't have the meaning he wants, but the pedant in me is screaming and I'm surprised it hasn't been said in the comments:

    In C++ mutable is a keyword.

  • by munchler on 10/31/25, 2:54 AM

    > Making almost every variable const at initialization is good practice. I wish it was the default, and mutable was a keyword.

    It's funny how functional programming is slowly becoming the best practice for modern code (pure functions, no side-effects), yet functional programming languages are still considered fringe tech for some reason.

    If you want a language where const is the default and mutable is a keyword, try F# for starters. I switched and never looked back.

  • by swiftcoder on 10/31/25, 8:03 AM

    Rediscovering one of the many great decisions that Erlang made
  • by ronnier on 10/31/25, 3:04 AM

    Immutability was gaining huge traction with Java... then large parts of the industry switched to golang and we hardly make anything immutable.
  • by TheRoque on 10/31/25, 2:07 PM

    I also default to const in javascript. Somehow a "let" variable feels so dirty, but I don't really know why. I guess at this point my experience forged an instinct but I can't even put a theory on it. But it's sometimes necessary and I use it of course.
  • by duxup on 10/31/25, 1:09 PM

    I'm going to maybe out myself as having limited experience here ...

    I don't mind the idea here, seems good. But I also don't move a block of code often and discover variable assignment related issues.

    Is the bad outcome more often seen in C/C++ or specific use cases?

    Granted my coding style doesn't tend to involve a lot of variables being reassigned or used across vast swaths of code either so maybe I'm just doing this thing and so that's why I don't run into it.

  • by vezcha on 11/2/25, 7:53 PM

    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.
  • by markstos on 10/31/25, 8:30 PM

    "State is the enemy".

    Every new state is an additional condition to check. A mutated variable implies an additional state to test.

    One boolean means two states to test. Four booleans that interact is 2^4 (16) states to test.

  • by acdbddh on 10/31/25, 2:03 PM

    In python its common to see code like this:

      df = pd.concat(df,other_df)
      df = df.select(...)
      ...
    
    My eyes hurts, when I see it. It makes me avoid python. I bet the reason for this mutable code is a missing simple pipes syntax. I love pipes. In R can do:

      df |> 
        rbind(other_df) |> 
        select(...)
    
    It feels much better.
  • by shermantanktop on 10/31/25, 2:55 PM

    Const/final by default - preach it, brother, amen and hallelujah. Mutability by default is the rule in the big languages and the world would be a better place if it weren’t.
  • by shmerl on 10/31/25, 3:29 AM

    Proposing making immutable by default in C or C++ doesn't make sense due to backwards compatibility reasons. New languages like Rust have easier time making better choices with immutable by default.
  • by storus on 10/31/25, 2:00 PM

    I had enormous fun optimizng C++ via self-modifying assembly to squeeze the utmost performance of some critical algorithms, and now this drive towards immutable everything feels like cutting my hands and legs off and forcing me to do subpar engineering.
  • by garrison on 11/2/25, 3:13 AM

    This reminds me of an earlier blog post by the same author: https://web.archive.org/web/20121111234839/http://www.altdev...
  • by waffletower on 10/31/25, 6:44 PM

    Would be beneficial if Rich Hickey's opinion and experience regarding mutable state was given more weight than John Carmack's.
  • by loeg on 10/31/25, 3:36 AM

    Yeah, I also wish it was the default. But it's a little too verbose to just sprinkle on every variable in C++. Alas. Rust gets this right, but I'm stuck with C++ at work.
  • by DonHopkins on 10/31/25, 10:32 AM

    Did you ever have one of those days when variables won't and constants aren't?
  • by Razengan on 10/31/25, 10:03 AM

    If designing your hypothetical ideal language, what are some intuitive/cute keywords you would choose for mutables/immutables?

    `let` is so 2020. `const` is too long. `static` makes me fall asleep at the keyboard. `con` sounds bad. How about

    `law`?

        law pi = 3.142 (heh typing on Mac autocompleted this)
    
        law c = 29979245
    
        law law = "Dredd"
    
    or `set` or `once` or `make`?
  • by Const-me on 10/31/25, 1:05 PM

    For performance critical code, you want to reuse L1D cache lines as much as possible. In many cases, allocation of a new immutable object boils down to malloc(). Newly allocated memory is unlikely to be found on L1D cache. OTOH, replacing data in recently accessed memory and reusing the memory is very likely to become L1D cache hit in runtime.
  • by nyrp on 10/31/25, 1:43 PM

    Is he referring to something specific with "true" iterative calculations vs. plain old iterative ones, assuming they are in some way "non-true" or less "true"? Like, avoiding i+=x in favor of ++i or something? Or maybe am I just tired today.
  • by thefaux on 10/31/25, 4:25 PM

    Even better is to use tail calls instead of loops and eliminate mutable variables entirely.
  • by rezonant on 10/31/25, 5:56 PM

  • by QuadrupleA on 10/31/25, 3:17 PM

    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.
  • by warmwaffles on 10/31/25, 2:02 PM

    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.
  • by considerdevs on 10/31/25, 10:17 AM

    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...

  • by koolba on 10/31/25, 10:42 AM

    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.

  • by noduerme on 10/31/25, 3:09 AM

    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.

  • by zahlman on 10/31/25, 3:22 PM

    > and it avoids problems where you move a block of code and it silently uses a version of the variable that wasn’t what it originally had.

    I find that keeping functions short also helps a ton with that.

    No, shorter than that. Short enough that the only meaningful place to "move a block of code" is into another function. Often, by itself.

  • by sgarland on 10/31/25, 10:47 AM

    Dumb question from a primarily Python programmer who mostly writes (sometimes lengthy) scripts: if you have a function doing multiple API calls - say, to different AWS endpoints with boto3 - would you be expected to have a different variable for each response? Or do you delete the variable after it’s handled, so the next one is “new?”
  • by AaronAPU on 10/31/25, 1:39 PM

    This would require coming up with an order of magnitude more variable names which is just unnecessary cognitive load.
  • by AtNightWeCode on 10/31/25, 9:39 PM

    I care less and less about things like this. At some point you will write code in some lang that have fancy keywords and stuff gets mutated anyway. Also, what people tends to do if stuff is immutable is that they hide mutation by doing deep copies with changes.
  • by carabiner on 10/31/25, 7:07 PM

    Does anyone have any real naming conventions, patterns for doing this in ds programming in notebooks? I've got a bad habit of doing:

      df = pd.read_excel()
      df = df.drop_duplicates.blahblah_other_chained_functions()
      [20 cells later]
      df = df.even_more_fns()
  • by stevage on 10/31/25, 3:51 AM

    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

  • by mcv on 10/31/25, 1:31 PM

    I use Javscript mostly. Or Typescript actually, these days. I remember when ES2015 introduced `let` because `var` had weird scoping issues. But ever since, I barely use either of them. Everything is `const` these days, as it should.
  • by wodenokoto on 10/31/25, 1:51 PM

    Mutability was by far the most difficult thing when learning Python and mutating objects by iterating over its items do get confusing, even as a senior.

    When I was first learning I thought all methods would mutate. It has a certain logic to it

  • by Havoc on 10/31/25, 9:36 AM

    Good point. Had never occurred to me that keeping steps help debug. Obvious in hindsight
  • by jdthedisciple on 10/31/25, 9:26 AM

    This is almost standard in modern languages:

    For example in Dart you make everything `final` by default.

  • by WalterBright on 10/31/25, 6:08 PM

    I'm curious where this fits in with single assignment semantics:

        int x = 3;
        x = 4; // error!
        int* p = &x;
        *p = 4; // is that an error?
  • by mr_mitm on 10/31/25, 8:36 AM

    Is there a ruff rule for this?
  • by halo on 10/31/25, 8:56 AM

    On a similar note, I’ve always liked the idea of being able to mark functions as pure (for some reasonable definition of pure).

    The principle of reducing state changes and side-effects feels a good one.

  • by MrNet32823 on 10/31/25, 10:19 AM

    Jonathan Blow had strong objection with const keyword. I forgot because i did not understand at that time. Does anyone with jai experience have a counter point to that.
  • by groby_b on 10/31/25, 5:39 PM

    It is really amazing in how many ways C/C++ made the wrong default/implicit choices, in retrospect.

    Hindsight's 20/20, of course. But still.

  • by bmitc on 10/31/25, 1:40 PM

    > I wish it was the default, and mutable was a keyword.

    Well yea, that's what sane languages that aren't Python, C, and C++ do. See F# and Rust.

  • by avadodin on 10/31/25, 6:05 PM

    We're all already doing this as the compiler turns everything into SSA form, silly goose.
  • by xd1936 on 10/31/25, 1:06 PM

    Wouldn't this allocate wasteful amounts of RAM unnecessarily for every step in a calculation?
  • by jodleif on 10/31/25, 10:50 AM

    This Carmack guy knows his stuff.
  • by ardit33 on 10/31/25, 9:41 AM

    Meh... I agree where he comes from (when working on large projects, muttability can introduce bugs), but many languages have solved it, similar by having two types, 'let' and 'var', or 'const' in front of a variable.

    But, there is practicality in the ability of being able to change a var, and not having to create a new object every time you change one of its members.

    It models real nature/physics better.

    It looks like He is asking that 'const' be the default, and 'var' should be explicit, which makes sense.

  • by mavhc on 10/31/25, 11:47 AM

    Really should invent a new name if you don't want your variables to vary
  • by kybernetyk on 10/31/25, 10:51 AM

    the comments/replies to his tweet remind me why I usually avoid twitter
  • by dinkblam on 10/31/25, 4:00 PM

    // !!!: SWIFTY VAR & LET SUPPORT

    #define var __auto_type

    #define let const __auto_type

  • by theodorethomas on 10/31/25, 12:24 PM

    Do as much as you can in a spreadsheet, then start a new spreadsheet.
  • by textlapse on 10/31/25, 3:14 PM

    I wish C++ did some sane things like if I have a const member variable, allow me to initialize it as I wish in my constructor - it's a constructor for crying out loud.

    Don't be silly and assume if I assign it multiple times in an if condition it's mutable - it's constructing the object as we speak, so it's still const!!!

    C# gets this right among many other things (readonly vs const, init properties, records to allow immutability by default).

    And the funny thing is the X thread has lots of genuine comments like 'yeah, just wrap a lambda to ensure const correctness' like that's the okay option here? The language is bad to a point it forces good sane people into seeing weird "clever" patterns all the time in some sort of an highest ELO rating for the cleverest evilest C++ "solution".

    I was hoping Carbon was the hail mary for saving C++ from itself. But alas, looks like it might be googlified and reorged to oblivion?

    Having said that, I still like C++ as a "constrained C++" language (avoid clever stuff) as it's still pretty good and close to metal.

  • by armaoin on 10/31/25, 7:24 PM

    Totally agree. const auto for me is the default.
  • by mtlmtlmtlmtl on 10/31/25, 4:50 PM

    It's fascinating how even when Carmack says something rather obvious and unoriginal, that many people have said before, sometimes decades ago, it still spawns a 400+ comment thread on HN. I really don't get it, it's almost like a cult of personality at this point.
  • by askmrsinh on 10/31/25, 5:07 AM

    Constants are useful for reasoning about code, but anyone who focuses only on making everything an immutable is missing the point. The main goal should be achieving referential transparancy.

    It can be perfectly fine to use mutable variables within a block, like a function when absolutely needed - for example, in JavaScript's try catch and switch statements that need to set a variable for later use. As long as these assignments are local to that block, the larger code remains side-effect free and still easy to reason about, refactor and mantain.

    https://rockthejvm.com/articles/what-is-referential-transpar...

  • by gethly on 10/31/25, 8:53 AM

    Variable is by definition mutable.

    Constant is by definition immutable.

    Why can't people get it through their heads in 2025? (I'm looking at you, Rust)

  • by cfontes on 10/31/25, 11:32 AM

    He will like RUST very much.
  • by reverseblade2 on 10/31/25, 1:25 PM

    Use F#, compile it to .net, rust, JavaScript, typescript and python. Problem solved.
  • by moi2388 on 11/1/25, 6:04 PM

    I agree. I mainly write c# nowadays, but even there I try to make everything as immutable as possible.

    Makes everything so much easier to reason about.

  • by KaiserPro on 10/31/25, 10:54 AM

    I don't mean to start a holy war, thats not the point, but isn't this a side effect of C++ footguns rather than python allowing you to be lazy?

    I mean there is good reason to keep variables well scoped, and the various linters do a reasonable job about scope.

    But I've only really know C++[1] people to want everything as a const.

    [1] Yes, you functional people also, but, lets not get into that.

  • by GTP on 10/31/25, 5:13 PM

    In other words, he wishes he used Rust.
  • by andsoitis on 10/31/25, 2:04 PM

    Is there another programming language that comes close to Clojure’s persistent data structures?
  • by oulipo2 on 10/31/25, 1:32 PM

    Can we stop linking the racist website that twitter has become?
  • by GPerson on 10/31/25, 3:34 AM

    Kinda curious on what jblow would say about this.
  • by smallstepforman on 10/31/25, 10:46 AM

    There are languages nobody uses, and languages people complain about. Computing is about change, otherwise there is nothing to compute. The mere fact that its called a “variable” makes it obvious that its supposed to change.
  • by hackthemack on 10/31/25, 1:32 PM

    One area that I like to have immutability is in function argument passing. In javascript (and many other languages), I find it weird that arguments in function act differently depending on if they are simple (strings, numbers) versus if they are complex (objects, arrays).

    I want everything that passes through a function to be a copy unless I put in a symbol or keyword that it suppose to be passed by reference.

    I made a little function to do deep copies but am still experimenting with it.

      function deepCopy(value) {
        if (typeof structuredClone === 'function') {
          try { return structuredClone(value); } catch (_) {}
        }
        try {
          return JSON.parse(JSON.stringify(value));
        } catch (_) {
          // Last fallback: return original (shallow)
          return value;
        }
      }