datarekha

Variables & Types

Python is dynamically typed, yet types still rule everything. Learn what a variable really is — a name, not a box — the core built-in types, and how to inspect them.

7 min read Beginner Python Lesson 3 of 41

What you'll learn

  • The difference between a variable and the value it points to
  • The six built-in types you will use almost all of the time
  • How to inspect a value's type with type() and isinstance()
  • Why 'is' and '==' ask two different questions

Before you start

It is tempting to picture a variable as a little box with a value tucked inside it. That picture is comforting, and in Python it is wrong — and the wrongness explains a whole family of surprises that catch people arriving from other languages. A variable here is not a box. It is a name, and the name points at an object that lives somewhere in memory.

That one shift — from boxes holding values to names pointing at objects — is worth slowing down for, because once you see it, behaviour that looked like a bug turns out to be the rule working exactly as designed.

A name points at an object

Watch what happens when we bind a second name to the same list and then change the list through that second name:

x = [1, 2, 3]
y = x            # y points at the SAME list as x
y.append(4)

print("x =", x)
print("y =", y)
print("same object?", x is y)
x = [1, 2, 3, 4]
y = [1, 2, 3, 4]
same object? True

Notice that appending through y changed what you see through x. If a variable were a box, that could not happen — x would have its own copy. But = never copies; it only binds another name to the object already there. So x and y are two names for one list, and x is y confirms it. To get a genuinely separate list you have to ask for one, with y = x.copy() or y = list(x).

namesxyone object in memory[1, 2, 3, 4]

y = x binds a second name to the same list — it never copies. Mutating through either name changes the one object.

The core built-in types

A handful of types carry the vast majority of real Python. These six show up everywhere:

TypeExampleMutable?
int42, -1000no
float3.14, 1e-9no
str"hello"no
boolTrue, Falseno
list[1, 2, 3]yes
dict{"a": 1}yes

You will also soon meet tuple ((1, 2), an immutable list), set ({1, 2}, unordered with unique elements), and None, the single object that stands for “no value at all.” You can always ask a value what it is:

n = 42
pi = 3.14159
greeting = "hello"
ok = True
items = [10, 20, 30]
user = {"name": "Aarav", "age": 28}

for value in (n, pi, greeting, ok, items, user):
    print(f"{type(value).__name__:<6}  {value!r}")
int     42
float   3.14159
str     'hello'
bool    True
list    [10, 20, 30]
dict    {'name': 'Aarav', 'age': 28}

Two small tools earned their place there. type(value).__name__ gives the type’s name as a plain string, and the !r inside the f-string calls repr() — the unambiguous representation, which is why 'hello' prints with its quotes. When you are inspecting values, repr tells you what something really is rather than how it prints.

Dynamically typed, but every value has a type

Python is dynamically typed: you never declare what type a variable holds, and the same name may point at an int now and a str a line later. But do not mistake that for “types don’t matter.” Every value has a firm type, and operations only run on compatible ones — the check simply happens when the line runs, not before:

x = 10
print(type(x))

x = "now I'm a string"
print(type(x))

result = "5" + 5
<class 'int'>
<class 'str'>
Traceback (most recent call last):
  ...
TypeError: can only concatenate str (not "int") to str

The name x happily changed type; what failed was asking to add a string and an integer. Because the error surfaces only at runtime, larger codebases add type hints — annotations that let mypy or pyright catch exactly this mistake before the program runs. That is a later lesson all its own.

Converting between types

When data crosses into your program from outside — a web form, a file, an API — it usually arrives as text, and you convert it deliberately:

age = int("28")           # str -> int
price = float("9.99")     # str -> float
flag = bool("")           # "" is False; ANY non-empty string is True
text = str(42)            # int -> str

print(age, price, flag, text)

int("twenty-eight")       # cannot be parsed as a number
28 9.99 False 42
Traceback (most recent call last):
  ...
ValueError: invalid literal for int() with base 10: 'twenty-eight'

Pause and predict

Before the last section, try this in your head: what does isinstance(True, int) return — and why might it surprise you? Hold a guess, then read on.

isinstance, not type ==

Prefer isinstance(x, T) over type(x) == T, because isinstance respects inheritance — and that matters the moment you start using classes:

isinstance(True, int)   # True  — bool is a subclass of int
type(True) == int       # False — exact type only

There is the answer to the prediction: True really is an int underneath (Python’s bool is a subclass of int), so isinstance says yes while the stricter type(...) == says no. Reaching for isinstance keeps your checks working as your types grow.

In one breath

  • A variable is a name that points at an object; = binds, it never copies.
  • == compares values; is compares identity — keep is for None.
  • Six types do most of the work: int, float, str, bool (immutable) and list, dict (mutable).
  • Python is dynamically typed, but every value has a type and type errors surface at runtime.
  • Convert with int(), float(), str(), bool() — and remember any non-empty string is truthy.

Practice

Quick check

0/4
Q1After `x = [1, 2]; y = x; y.append(3)`, what is `x`?
Q2Which expression is the idiomatic 'is this None?' check?
Q3What does `bool('False')` return?
Q4After `a = (1, 2, 3); b = a`, which operation raises a TypeError?

What’s next

You know what a variable is and the types it can point at. Next we look hard at numbers — where 0.1 + 0.2 famously does not equal 0.3, and why that is mathematics, not a Python bug.

Sign in to track your progress

Completed lessons, your XP, level, and streak save to your account — it's free and takes a few seconds.

Practice this in an interview

All questions
What is the difference between `is` and `==` in Python, and when does `is` give a surprising result?

`==` calls `__eq__` and tests value equality. `is` tests object identity — whether two names point to the exact same object in memory. The surprise comes from CPython's interning: small integers (-5 to 256) and many short strings are cached, so `is` returns True even for separately created objects, but this is an implementation detail you must never rely on.

Which Python built-in types are mutable and which are immutable, and why does it matter?

Immutable types — int, float, bool, str, bytes, tuple, frozenset — cannot be changed after creation; operations return new objects. Mutable types — list, dict, set, bytearray — can be changed in place. Mutability determines hashability (only immutables can be dict keys/set members), function side-effect behaviour, and thread-safety considerations.

What is the mutable default argument trap in Python, and how do you fix it?

Default argument values are evaluated once when the function is defined, not each time it is called. If the default is a mutable object like a list or dict, all calls that use the default share the same object — so mutations in one call persist into the next. The fix is to use None as the default and create the mutable object inside the function body.

In Python, do variables store values or references to objects?

Python variables are names — they store references (pointers) to objects, not the objects themselves. Assignment binds a name to an object; it never copies the object. Understanding this explains why mutating an object through one name is visible through all other names that reference the same object.

Related lessons

Explore further

Skip to content