A variable is a named binding that associates an identifier with a value or memory location, allowing that value to be stored, retrieved, and optionally modified throughout the execution of a program, with a scope that defines where the binding is visible and a lifetime that defines how long the storage persists.
What a variable is — across all languages
What you'll learn: The universal concept of a variable — a named container for a value. How variables work in Python vs JavaScript vs Java, and the critical difference between mutable and immutable variables.
How to read this tab: This is a concepts page — it explains an idea that exists in every programming language, not Python-specific syntax. Read it alongside or after the Python Variables & Types reference page.
A variable is a label stuck on a box — the label is the name you use, the box is the memory that holds the value, and the contents of the box can change while the label stays the same.
The analogy that makes everything else click: a variable is a label stuck on a box. The label is the name (user_name). The box is the memory location that holds the value ("Priya"). The box contents can change; the label stays. In Python, multiple labels can point to the same box — which is why a = b = [] gives you two labels on one list.
What a variable actually is
When you write x = 5, you're doing two things: reserving a piece of memory to hold a value, and creating a name (x) that refers to it. The name is not the value — it is a reference to wherever the value lives. This association between a name and a value (or location) is called a binding.
In Python, x = 5 makes x point to the integer object 5. In C, int x = 5 reserves a specific memory location (4 bytes on most systems), labels it x, and stores the binary representation of 5 there. The concept — name binds to value — is the same. The mechanism is different.
x = 5 # bind the name 'x' to the integer 5
y = x # bind 'y' to the same value
x = 10 # rebind 'x' to a new value — 'y' is unaffected
print(x) # 10
print(y) # 5 — y still points to the original 5
Mutability is about the VALUE, not the variable name. In Python, integers and strings are immutable — when you "change" a string, Python creates a new string object and points your variable at it. Lists are mutable — you change the same object in place. This distinction determines what happens when you pass a variable to a function.
Mutable and immutable variables
A mutable variable can have its value changed after it is created. A variable in Python, JavaScript, or Java can be assigned a new value at any time. This is what most people picture when they think of a variable — a box whose contents you can swap out.
Some languages offer immutable bindings — once assigned, the name cannot be rebound to a different value. In Rust, variables are immutable by default: let x = 5 creates a binding that cannot be changed. To allow changes, you must explicitly opt in with let mut x = 5. In JavaScript, const name = "Alice" prevents name from being reassigned. In Java, final int x = 5 prevents reassignment.
In JavaScript, const arr = [1, 2, 3] prevents arr from being reassigned — but arr.push(4) still works. const makes the binding immutable, not the data. The array itself is still mutable. True immutability requires immutable data structures, not just immutable bindings.
Every language has scope rules — they differ significantly. Python: no block scope (variables in if/for are visible outside). JavaScript: var has function scope, let/const have block scope. Java/Go: block scope everywhere. Understanding your language's scope rules prevents the most common category of "why is this undefined?" bugs.
Scope: where a variable is visible
A variable's scope is the region of the program where its name is visible and accessible. A variable defined inside a function typically cannot be seen outside that function. A variable defined at the top level of a file may be visible everywhere in that file.
This prevents accidental name collisions. If every function could see every variable from every other function, large programs would be impossible to maintain — changing any variable name would risk breaking code anywhere else in the program.
Scope and lifetime are different things. Scope is about visibility in code. Lifetime is about when memory is allocated and freed. In Python, memory is freed by the garbage collector when no more references exist. In C, memory on the stack is freed when the function returns. In C++/Rust, lifetime is explicitly managed.
Lifetime: how long a variable's storage persists
Related to scope but distinct from it: a variable's lifetime is how long the memory backing it is kept alive. In most languages, local variables live as long as the function that created them — when the function returns, its local variables are destroyed. In some languages (C, Rust), the programmer has explicit control over this. In others (Python, Java), a garbage collector automatically frees memory when no variable references an object any more.
Studying variables across languages reveals the underlying concepts. Python uses dynamic typing (type known at runtime). Java uses static typing (type declared at compile time). JavaScript has both var (function scope, hoisted) and let/const (block scope). Seeing the differences makes you understand what each choice trades off.
Variables across languages
The concept is universal; the syntax and rules vary:
Python: x = 5 # no declaration keyword; dynamic type
JavaScript: let x = 5 # block-scoped mutable
JavaScript: const x = 5 # block-scoped immutable binding
Java: int x = 5 # static type declared; mutable
Java: final int x = 5 # immutable binding
C: int x = 5; # memory address; mutable
Rust: let x = 5; # immutable by default
Rust: let mut x = 5; # mutable; type inferred as i32
Go: x := 5 # short declaration; inferred type int
Swift: let x = 5 # immutable (constant)
Swift: var x = 5 # mutable
Scope, lifetime, and how languages differ
What you'll learn: Variable scope (where a variable is visible) vs lifetime (how long it lives in memory). How Python, JavaScript, Java, and Go each handle scope differently.
How to read this tab: The scope section connects directly to Python's LEGB rule. Read the Functions & Scope reference page alongside this for the Python-specific detail.
The formal distinction: a binding maps a name to a value. A location is actual memory. Most languages conflate them for simplicity. Functional languages (Haskell, Clojure) make bindings immutable — a name always refers to the same value once bound. This eliminates a whole class of bugs at the cost of a different programming style.
Bindings vs. locations
The distinction between a binding (name → value) and a location (name → memory address → value) is fundamental to understanding how different languages behave.
In Python, all variables are bindings to objects. Assignment does not copy — it rebinds the name to point to a (possibly different) object. Two names can point to the same object. When you write y = x, both y and x point to the same object. If that object is mutable (a list), modifying it through x is visible through y. If it is immutable (an integer), this has no visible effect.
In C, variables are named memory locations. int x = 5 allocates storage and fills it. y = x copies the value from x's location to y's location — two independent locations now holding 5. Modifying x does not affect y.
# Mutable object — binding semantics matter
a = [1, 2, 3]
b = a # b and a point to the SAME list
b.append(4)
print(a) # [1, 2, 3, 4] — a sees the change!
# Immutable object — no visible difference
x = 5
y = x
x = 10
print(y) # 5 — y still points to the original 5
# id() shows the identity (memory address) of the object
print(id(a) == id(b)) # True — same object
The Temporal Dead Zone (TDZ) is JavaScript-specific. In JavaScript, let and const declarations are hoisted but not initialised — accessing them before the declaration is a ReferenceError. This is the TDZ. Python has no equivalent — names are simply not defined until the assignment is executed.
Scope rules by language
Most modern languages use lexical scope (also called static scope): the scope of a variable is determined by its position in the source code, not by the runtime call chain. The compiler or interpreter can determine which declaration any name refers to by reading the text of the program.
Python uses the LEGB rule: look up a name in Local scope first, then Enclosing function scopes, then Global (module) scope, then Built-in scope. The first match found is used. JavaScript (with let and const) uses block scope — a variable is visible only within the block ({ ... }) where it is declared, not in the surrounding code.
x = "global" # G — global scope
def outer():
x = "enclosing" # E — enclosing scope
def inner():
x = "local" # L — local scope
print(x) # "local" — L found first
inner()
print(x) # "enclosing" — E is inner's enclosing; outer's local
outer()
print(x) # "global"
Python hides this from you — but it's happening underneath. Python objects live on the heap. The call stack contains frames that hold references to heap objects. Languages like C and Rust expose this directly — stack allocation is fast but size-limited; heap allocation is flexible but requires manual management (C) or compiler-tracked lifetimes (Rust).
Stack vs. heap allocation
Most local variables are allocated on the stack — fast, automatically managed, and freed when the function returns. Objects with longer lifetimes are allocated on the heap — managed explicitly (C, C++) or by a garbage collector (Java, Python, Go, JavaScript).
Rust's ownership system eliminates the need for a garbage collector by making each memory location have exactly one owner. When the owner variable goes out of scope, the memory is freed immediately. No GC pause, no dangling pointers, no double-free — enforced at compile time.
x is the name. The value 5 is what x currently refers to. The variable continues to exist after the assignment; the value does not change unless the variable is reassigned. After x = 10, the variable x still exists — it now refers to a different value.const in JavaScript is not immutable data. const prevents the variable from being reassigned — the binding is frozen. But if the value is a mutable object (array, object), that object can still be modified. True immutability requires immutable data structures.x = 5 in Python is both declaration and assignment simultaneously. There is no int x; step. Variables come into existence the moment they are first assigned. Accessing a name before it is assigned raises a NameError.Bindings vs locations, the stack vs heap, and Rust's ownership
What you'll learn: The formal distinction between a name binding (a label) and a storage location (memory). Stack vs heap allocation. How Rust's ownership system eliminates a whole class of variable-related bugs.
How to read this tab: Read after Stage 2. The l-value/r-value distinction and Rust's borrow checker are advanced concepts relevant when you study systems programming.
l-values and r-values
The C and C++ standards distinguish between l-values (expressions that denote a memory location — can appear on the left side of an assignment) and r-values (expressions that denote a value — can only appear on the right side). x = 5 is valid because x is an l-value. 5 = x is invalid because 5 is an r-value. Modern C++ extends this with rvalue references (&&) enabling move semantics — transferring ownership of a resource without copying.
Rust's ownership and borrow checker
Rust's variable system is defined by three rules (The Rust Reference, Chapter 10 — Ownership): every value has exactly one owner; when the owner goes out of scope, the value is dropped (freed); ownership can be transferred (moved) or temporarily lent (borrowed). The borrow checker is a compiler pass that verifies these rules statically. A mutable borrow (&mut x) is exclusive — no other references, mutable or immutable, may coexist with it. An immutable borrow (&x) can coexist with any number of other immutable borrows but not with a mutable borrow.
Python variable semantics: Python Language Reference §3.1 — Names and Binding (docs.python.org/3/reference/executionmodel.html). Rust ownership: The Rust Reference, Chapter 10 (doc.rust-lang.org/reference/). C l-values and r-values: ISO/IEC 9899:2018 (ISO C17) §6.3.2.1.
✅ Expert tab complete
- I can explain the difference between a name binding and a memory location
- I know what l-values and r-values mean in C/C++
- I understand at a high level how Rust's ownership model prevents use-after-free bugs
Sources
let, const, and block scope.