Value vs Reference, Pointers & Mutability 🎯

One of the most frequent sources of unexpected bugs is not knowing whether a variable holds an actual copy of data or a pointer/reference to shared data.


1. The Real-World Analogy: Photocopier vs Google Doc

  • Pass-by-Value (The Photocopier): You take a printed document, photocopy it, and hand the copy to a teammate. If they highlight text or spill coffee on their copy, your original document is 100% untouched.
  • Pass-by-Reference / Pointer (The Google Doc URL): You send a teammate a link to a shared Google Doc. If they change a paragraph, you instantly see that change in your browser because you are both editing the exact same document.
PASS-BY-VALUE:
Original: [ 42 ] ──(Copy)──▶ Function Param: [ 42 ] (Independent copy)

PASS-BY-REFERENCE / POINTER:
Original Variable ──┐
                    ├──▶ Shared Memory [ Alice, Admin ] (Both see changes)
Function Param    ──┘

2. Pointers Explained in Plain English

A pointer is simply a variable whose value is the memory address of another variable.

Think of it like a piece of paper with someone’s home address written on it:

  • The value is the physical house (with furniture, rooms).
  • The pointer is the slip of paper saying "123 Maple Street".
  • Passing the slip of paper around is lightweight (only 8 bytes). Anyone holding the address can visit the house and change the furniture (dereferencing).

Example in Go

package main
 
import "fmt"
 
func updatePrice(price int) {
	price = 200 // Modifies a local copy!
}
 
func updatePriceWithPointer(pricePtr *int) {
	*pricePtr = 200 // Follows the address and modifies the original house!
}
 
func main() {
	itemPrice := 100
 
	updatePrice(itemPrice)
	fmt.Println(itemPrice) // 100 (Unchanged!)
 
	updatePriceWithPointer(&itemPrice) // & gets the address of itemPrice
	fmt.Println(itemPrice) // 200 (Modified!)
}
  • &x means: “Give me the memory address of x.”
  • *ptr means: “Go to the address inside ptr and read/write the value there.”

3. The “Pass-by-Sharing” Trap in Python and JavaScript

In Python, JavaScript, and Java, you don’t write explicit * or & syntax. Instead:

  • Primitive values (numbers, booleans, strings) behave like values.
  • Collections and objects (lists, dictionaries, objects) behave like shared references.

The Classic Shared Mutation Bug

def add_admin_role(user_roles):
    user_roles.append("admin")  # Mutates the caller's list!
 
current_roles = ["editor"]
add_admin_role(current_roles)
 
print(current_roles)  # Output: ['editor', 'admin'] — surprising side-effect!

The Default Argument Trap in Python

In Python, default arguments are created once when the file loads, not every time the function runs:

# ❌ BUG: The same list is shared across every call!
def register_user(username, tags=[]):
    tags.append(username)
    return tags
 
print(register_user("alice")) # ['alice']
print(register_user("bob"))   # ['alice', 'bob'] ⚠️ Alice's tag leaked to Bob!
 
# ✅ FIX: Use None as default and create a fresh list inside
def register_user_safe(username, tags=None):
    if tags is None:
        tags = []
    tags.append(username)
    return tags

4. Mutable vs Immutable Data

QualityMutable DataImmutable Data
DefinitionCan be changed in place after creationCannot be modified once created
ExamplesPython list, dict; Go slice, map; JS ArrayPython tuple, str, int; Go string; Java String
ProsFast in-place updates, memory efficientThread-safe, predictable, easy to reason about
ConsUnintended side effects across functionsChanging a value requires creating a new copy

Why Modern Systems Love Immutability

In concurrent or distributed programming (like multiple threads or goroutines handling web requests), immutable data requires zero mutexes or locks. If no thread can modify a piece of data, dozens of threads can read it simultaneously with zero risk of corrupted data!


Across the Wiki