Differences between const, let, var, and let mut in the 20 most popular programming languages

Every mainstream language has a way to say “this binding cannot be reassigned” — and almost none of them agree on how. The keyword map below replaces the one from the original 2023 version of this post, which glossed over the real differences (claiming, among other things, that Python and Go have a let). This version lists the actual keywords per language.

The real keyword map

Language Immutable binding Mutable binding Notes
C const plain declaration const is read-only at runtime, not a compile-time constant — use enum or #define for real constants.
C++ const, constexpr plain declaration constexpr gives true compile-time constants; mutable carves exceptions out of const objects.
Java final plain declaration (var for inference) var is Java 10+, local variables only — it says nothing about immutability.
Python none — UPPER_CASE convention, typing.Final plain assignment No declaration keywords at all; Final is a static hint, not enforced at runtime.
JavaScript const let var is legacy: function-scoped and hoisted. Prefer const/let.
TypeScript const let Adds readonly properties and as const for deep immutability.
PHP const / define() plain $var Typed class constants arrived in PHP 8.3.
Ruby capitalized identifier (MAX = 100) plain local variable Reassignment warns instead of erroring; freeze protects the object itself.
Swift let var
Kotlin val var
C# const (compile-time), readonly (runtime) plain declaration, var inference
Go const var or := No let; constants are compile-time and untyped by default.
Rust const, static let; let mut for mutable Bindings are immutable by default — the only mainstream language where this is true.
R none (lockBinding()) <- assignment Constants are simulated with locked bindings.
MATLAB none plain assignment The properties (Constant) class pattern is the closest thing.
Objective-C const, often with extern plain declaration #define is the legacy alternative.
Perl use constant my $x my is lexical scoping, not immutability.
Scala val var
Shell (bash) readonly plain assignment local is scoping and often mistaken for immutability.
SQL none DECLARE (T-SQL), @var (MySQL) Standard SQL has no session variables; dialects differ.
Lua local x <const> (5.4+) local x or global The <const> attribute is new in Lua 5.4.
PowerShell Set-Variable -Option Constant $var = ... No declaration keywords; everything is $name.
Visual Basic Const Dim

The gotchas worth knowing

  • JavaScript/TypeScript: const binds the reference, not the value — const x = [] still lets you x.push(). Deep immutability needs Object.freeze() (or as const in TypeScript).
  • Rust: immutability is the default, so let mut is opt-in. const and static are compile-time items, not bindings. Shadowing lets you re-let a name with a new value — deliberately not the same thing as mutation.
  • C vs C++: in C, const creates a read-only object, not a compile-time constant (a C89 array can’t be sized with one). C++ added constexpr for the real thing.
  • C#: const values are baked into consuming assemblies at compile time; readonly fields are evaluated at runtime. Changing a public const requires recompiling every consumer — prefer static readonly for anything that might change.
  • Java: final is the whole story. var (Java 10+) is purely about local type inference and says nothing about mutability.
  • Python: immutability is convention (UPPER_CASE) plus static hints — typing.Final (3.8+) and @dataclass(frozen=True). Nothing is enforced at runtime.
  • Ruby: constants are just capitalized identifiers; reassignment only warns, and the referenced object stays mutable unless you freeze it.
  • Bash: readonly is the immutability keyword; local is scoping and gets confused for it.

Why let mut is Rust-only

The original title bundled let mut in with the other keywords because Rust makes it famous — but it is the odd one out precisely because Rust flips the default: everywhere else, variables are mutable unless you mark them immutable, while in Rust they are immutable unless you mark them mutable. Swift and Kotlin split the same job across let/var and val/var; Rust just spells the mutable side out.

Conclusion

There is no portable mental model for “the const keyword” — only per-language conventions. The reliable questions to ask of any new language are: which keyword makes a binding immutable, is immutability deep (the object) or shallow (the binding), and is the constant compile-time or runtime. The table above answers the first question for the 20+ languages most of us touch; the gotchas cover the other two where they bite.

Leave a Reply

Your email address will not be published. Required fields are marked *