Skip to main content
Variables, Types, and the Memory Behind Them

Absolute Foundations

Variables, Types, and the Memory Behind Them

Reading12 min read

Variables, Types, and the Memory Behind Them

A variable isn't a box that holds a value — it's closer to a label pointing at a value somewhere in memory. That distinction sounds academic until it causes a bug that only shows up in production, usually involving a list or object that changed when nothing in your code seemed to touch it.

Value Types vs. Reference Types

Primitive values — integers, floats, booleans, and (in most languages) strings — are typically value types. When you assign one variable to another, you get an independent copy.

Objects, arrays, and lists are usually reference types. Assigning one variable to another copies the reference (the address), not the underlying data. Both variables now point at the same object.

// Value type — independent copies
let a = 5;
let b = a;
b = 10;
console.log(a); // 5 — unaffected

// Reference type — shared underlying data
let list1 = [1, 2, 3];
let list2 = list1;
list2.push(4);
console.log(list1); // [1, 2, 3, 4] — also changed!

This is one of the most common early bugs across every mainstream language: passing a list or dict into a function, "modifying a copy" inside it, and being surprised when the caller's original data changed too — because there was never a copy, just a second label on the same object.

def add_discount(cart_items):
    cart_items.append("discount-applied")  # mutates the caller's list in place
    return cart_items

original_cart = ["book", "pen"]
result = add_discount(original_cart)
print(original_cart)  # ["book", "pen", "discount-applied"] — the caller's list changed too

If you actually want an independent copy, you have to ask for one explicitly — list(original) in Python, [...original] or structuredClone(original) in JavaScript, .clone() on a Java collection. "Copy" is never the silent default for reference types.

Static Typing vs. Dynamic Typing

A statically typed language (Java, TypeScript, Go, Rust) checks that your types are consistent before the program runs — you can't assign a string to a variable declared as int and have it compile. A dynamically typed language (Python, JavaScript, Ruby) checks types while the program runs, which means a type mismatch can slip through until the exact line that hits it executes.

// TypeScript — this fails at compile time, before you ever run it
let age: number = "twenty-five"; // Type 'string' is not assignable to type 'number'
# Python — this fails only when the line actually runs
def get_age():
    return "twenty-five"

age = get_age()
next_year = age + 1  # TypeError, but only raised when this line executes

Neither approach is strictly "better" — static typing catches a class of bugs earlier and makes large codebases easier to refactor safely; dynamic typing trades that safety net for less ceremony and faster iteration, especially on smaller codebases. What matters is knowing which one you're working in, because it changes where and when type bugs will surface.

Why This Is Worth Internalizing Early

Nearly every "the data changed and I don't know why" bug traces back to an unexpected shared reference. Once you can look at a line of code and know whether it's copying a value or sharing a reference, an entire category of confusing bugs stops being confusing.

💬 Discussion

Have you been bitten by an unintended shared reference — a list or object changing when you thought you were working on a copy? What was the fix?

Q
Knowledge Check
1 / 3

After running `let list2 = list1; list2.push(4);` in JavaScript, why does `list1` also show the new element?

Next Lesson

Control Flow Done Right

How Computers Actually Run Your Code