› Module 2: Data Types & Variables › 2.4 Type Casting & Operators
M O D U L E

Module 2: Data Types & Variables

Dynamic typing mechanics, mutable vs immutable types, the 'variables as name tags on balloons' memory model, and type casting.

2.1 Dynamic Typing

In a statically typed language like Java or C, you must announce a variable's type before you use it — int x = 5; tells the compiler, permanently, "x will always hold a whole number."

Python takes the opposite approach. You simply write x = 5, and Python looks at the value on the right-hand side and infers the type automatically — no announcement needed. This is called dynamic typing, and it goes one step further than most beginners expect:

The same variable name can be reassigned to a completely different type later in the program. x = 5 followed later by x = "hello" is perfectly legal Python — the name x simply now refers to a string instead of a number.

# Dynamic Typing in Action
x = 5             # x refers to an int object
print(type(x))    # <class 'int'>

x = "ReadyToGo"   # x now refers to a string object
print(type(x))    # <class 'str'>

x = [1, 2, 3]     # x now refers to a list object
print(type(x))    # <class 'list'>
The trade-off (from the Guide):

Dynamic typing makes Python fast to write and flexible to iterate on, but it also means a type-related mistake (like accidentally adding a number to a string: 5 + "10") is only caught when that exact line actually runs, not ahead of time the way a compiler would catch it.

Type Hints & Static Analysis (PEP 484)

In modern Python (3.5+), you can add optional type hints to make your intent crystal clear to IDEs and tools like mypy, without losing runtime dynamic flexibility:

def calculate_total(price: float, tax_rate: float) -> float:
    return price * (1.0 + tax_rate)

Python does not enforce these types at runtime, but static analysis tools will flag potential mismatches before code is deployed.

2.2 Built-in Data Types: Mutable vs Immutable

Every one of Python's built-in types falls into one of two camps, and understanding which camp a type belongs to explains a huge amount of Python's behavior later on.

1. Immutable Types

Immutable types — int, float, bool, str, and tuple — can never be changed after they are created. When code appears to "modify" a string or a number, what's actually happening behind the scenes is that Python builds a brand-new object and simply points your variable at it instead.

2. Mutable Types

Mutable types — list, dict, and set — are different: their contents really can be changed, grown, or shrunk in place, without creating a new object at all.

Python's Built-in Data Types Immutable int, float, bool, str, tuple (cannot change after creation) Mutable list, dict, set (can change in place)
Figure 2.1 — Every value in Python is an object; whether that object's content can ever change is what defines its type category.

Why Immutability Matters in Architecture

  • Hashability & Dictionary Keys: In Python, only immutable objects are hashable (they implement __hash__). Because an immutable object's content can never change, its hash value remains constant for its entire lifetime. This makes int, str, and tuple safe to use as dictionary keys or set elements, while list and dict will raise TypeError: unhashable type: 'list'.
  • Thread Safety: Immutable objects can be safely shared across multiple threads without locks or data race conditions because no thread can mutate their state.

2.3 Variables as References (Name Bindings)

This is the single most important mental model shift when moving from C-like languages into Python. In C, a variable is a labeled box: int x = 10; physically reserves a slot in memory and puts the value 10 directly inside it.

In Python, a variable is not a box — it's a name tag pointing at an object that lives somewhere else in memory. Writing x = 10 creates an integer object with the value 10, and then attaches the name x to it, like tying a label onto a balloon.

The Shared Reference Aliasing Trap

Now consider y = x. This does not copy the value into a new box called y — it ties a second label onto the exact same balloon. Both x and y now refer to the identical object in memory.

x = 10; y = x — both names point to the SAME object x y int object value = 10
Figure 2.2 — Variables are labels bound to objects in memory, not fixed-size boxes holding a value directly.

Why This Distinct Model Produces Classic Beginner Bugs

For immutable objects like integers this distinction rarely causes visible problems, because you can't change the object anyway. But for a mutable object — say, a list — this is exactly why a classic beginner bug appears: modifying the list through the name y is instantly visible when you look at it through the name x too, because there was only ever one list, with two labels on it:

# Classic Mutable Aliasing Bug
list_a = [10, 20, 30]
list_b = list_a       # Ties label list_b onto the SAME list object

list_b.append(40)     # Modifies the single shared object in-place

print(list_a)         # Output: [10, 20, 30, 40] — list_a changed too!
print(list_a is list_b) # Output: True (Identical memory address)

# How to make an actual independent copy:
list_c = list_a.copy() # or list_a[:]
list_c.append(99)
print(list_a)         # [10, 20, 30, 40] — protected from mutation!

2.4 Type Casting & Operators

Because Python infers types automatically, you sometimes need to explicitly convert — or cast — a value from one type to another. Python provides simple built-in functions for this:

  • int("5") turns the text "5" into the number 5.
  • float("3.14") turns text into a decimal number 3.14.
  • str(42) turns a number into text "42".
  • list("abc") turns a string into a list of its individual characters ['a', 'b', 'c'].

Operators in Python

Python supports the operators you'd expect from any language: arithmetic (+ - * / % ** //), comparison (== != > < >= <=), and logical operators — though logical operators are spelled out as English words (and, or, not) rather than symbols like &&, ||, !.

Two Special Operators to Highlight

Two operators are worth calling out specifically because C and Java don't have direct equivalents:

  • // performs floor division: division that rounds down to the nearest whole number.
  • ** performs exponentiation: raises the base to the power of the exponent.
# ARITHMETIC OPERATORS
x = 7
print(x / 2)   # 3.5  (true division — always returns a float!)
print(x // 2)  # 3    (floor division — rounds down to nearest whole number)
print(x ** 2)  # 49   (exponentiation, x squared)

💡 Placement & Interview Deep-Dive

  • Negative Floor Division: What is -7 // 2? Beginners often assume -3. In Python, floor division rounds down toward negative infinity, so -7 // 2 evaluates to -4!
  • Arbitrary-Precision Integers: In Python 3, integers have unbounded precision! You can calculate 2 ** 1000 without any integer overflow error, unlike Java or C where integers wrap around at 32 or 64 bits.
  • Short-Circuit Evaluation of and / or: In Python, logical operators return the value of the operand that determined the outcome, not just a raw boolean:
    print("" or "Default") # Output: "Default"
    print("Ready" and "Go") # Output: "Go"
MODULE COMPREHENSIVE QUIZ

Module 2 Final Quiz

Test your understanding across all 4 topics of this module with interview-grade questions, tricky edge cases, and code output predictions.

Take Module 2 Final Quiz →
Yukti Mascot