🐍 Python Course · Stage 4 · Lesson 59 of 89 · Concept: Variables
Course Overview →

🟩 These concept pages connect Python to programming in general. Read Beginner tab first.

← http.server — HTTP Server Concept: Data Types →
thecodex.expert  ·  The Codex Family of Knowledge
CS Concepts

Variables

The most fundamental building block of every program: a name that stands for a value.

CS Concepts No prerequisites ~16 min read Last verified:
Canonical Definition

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.

🟩 Beginner

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.

⏱ 20 min 📄 2 sections 🔶 Prerequisite: None
One sentence that captures it

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.

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

Common mistake

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:

Multi-languagevariables_comparison.txt
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
🔵 Intermediate

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.

⏱ 20 min 📄 3 sections 🔶 Prerequisite: Beginner tab
📎

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.

Pythonbinding_vs_copy.py
# 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.

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

Commonly confused
A variable is not the same as a value. The variable 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.
Python has no variable declaration. Writing 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.
🔴 Expert

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.

⏱ 20 min 📄 4 sections 🔶 Prerequisite: After Stage 2

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.

Specification reference

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.

How this connects
Formal basis
Enables
Source confidence: High Last verified:

✅ 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

Continue to Data Types →

Sources

1
Python Software Foundation. The Python Language Reference, §3.1 — Names and Binding. docs.python.org/3/reference/executionmodel.html. — Authoritative reference for Python variable binding semantics.
2
The Rust Reference, Chapter 10 — Ownership and Lifetimes. doc.rust-lang.org/reference/. — Authoritative specification for Rust ownership, borrowing, and lifetimes.
3
ISO/IEC 9899:2018 (ISO C17). §6.3.2.1 — Lvalues, arrays, and function designators. International Organization for Standardization. — Formal C specification for l-values and r-values.
4
ECMAScript 2024 Language Specification (ECMA-262), §13.15 — Assignment Operators; §14.3 — Declarations. Ecma International. ecma-international.org. — Formal JavaScript specification for let, const, and block scope.