Technology  /  Python

🐍 Python 78 guides · updated 2026

From first variable to OOP, generators, and real projects — the language that runs everything from data pipelines to AI agents, taught the practical way.

Dynamic Typing and Strong Typing in Python: What They Mean in Practice

Python gets described as “dynamically typed” and “strongly typed” — and both descriptions are accurate but mean completely different things. Understanding the distinction isn’t just academic; it explains why certain operations raise errors while others work fine, and it shapes how you write Python that’s reliable.


Dynamic Typing: Types Are Resolved at Runtime

In a statically typed language like Java or C, you declare a variable’s type when you write the code:

int count = 5;
count = "five"; // Compile error — count can only hold ints

The compiler rejects the second line before the program ever runs.

Python takes a different approach. Types are attached to values, not to variable names. A variable is just a label pointing to an object, and you can point it at a different type of object whenever you want:

# This is completely valid Python
value = 42
print(type(value)) # <class 'int'>
value = "forty-two"
print(type(value)) # <class 'str'>
value = [1, 2, 3]
print(type(value)) # <class 'list'>

The name value has no type. The objects 42, "forty-two", and [1, 2, 3] have types. Python checks those types at the moment operations are performed — that’s the “runtime” part of “dynamic typing.”

What this means in practice

def process(data):
# Python won't tell you data's type until this function runs
return data.upper() # works if data is a string, fails otherwise
process("hello") # "HELLO"
process(123) # AttributeError: 'int' object has no attribute 'upper'

The error only surfaces when you call the function with an integer. This is the trade-off of dynamic typing: you gain flexibility and brevity, but type errors appear at runtime rather than during a compilation step.


Strong Typing: Python Won’t Silently Coerce Types

Here’s where Python differs from JavaScript and PHP. Being dynamically typed doesn’t mean Python will try to guess what you meant when types don’t match:

# JavaScript: "5" + 10 = "510" (string concatenation)
# Python: raises TypeError
result = "5" + 10 # TypeError: can only concatenate str (not "int") to str

Compare this to JavaScript’s implicit type coercion:

console.log("5" + 10) // "510" — silently converts 10 to a string
console.log("5" - 10) // -5 — silently converts "5" to a number

Python refuses to guess. If you want to add a string representation of a number to another string, you must make that explicit:

age = 25
message = "You are " + str(age) + " years old" # explicit conversion
# or better:
message = f"You are {age} years old" # f-string handles it

This strictness prevents an entire category of silent bugs. When Python raises a TypeError, it’s catching an error that a weakly typed language would turn into subtly wrong output.


The Key Distinction

The combination sounds contradictory but makes sense once you see it this way:

A language can be dynamic and weak (PHP, early JavaScript), dynamic and strong (Python, Ruby), or static and strong (Java, Rust). Each combination has different trade-offs.

# Python's type rules in action
print(True + 1) # 2 — bool is a subclass of int, this is intentional
print(1 + 1.5) # 2.5 — int to float promotion, explicitly defined
print("hello" + 1) # TypeError — Python draws the line here

Type Hints: The Best of Both Worlds

Python 3.5+ supports optional type annotations. They don’t change how Python runs code, but tools like mypy can check them statically, giving you early error detection without changing the dynamic nature of the language:

def calculate_discount(price: float, rate: float) -> float:
"""Return price after applying discount rate (0–1)."""
return price * (1 - rate)
# mypy would flag this call as a type error before running
result = calculate_discount("19.99", 0.1) # mypy: Argument 1 has incompatible type "str"

At runtime, the annotation does nothing. But with a type checker in your workflow, you get early warnings about type mismatches. This is increasingly common in professional Python codebases.


Where Dynamic Typing Causes Real Bugs

The most common dynamic typing bug looks like this:

def total_price(items):
total = 0
for item in items:
total += item["price"] # crashes if price is stored as a string
return total
# Works fine
total_price([{"price": 10.99}, {"price": 5.49}])
# Crashes at runtime — no warning beforehand
total_price([{"price": "10.99"}, {"price": "5.49"}])

The fix is explicit validation or type conversion:

def total_price(items):
total = 0
for item in items:
total += float(item["price"]) # convert explicitly
return total

Dynamic typing rewards careful, consistent data handling. When the data flowing through your program has consistent types — which it usually does in well-structured code — dynamic typing gets out of your way entirely.


Practical Takeaways

Dynamic typing makes Python fast to write. Strong typing makes Python safe to run. Together, they mean:

The developers who struggle with Python’s type system are usually expecting either the rigidity of Java (where the compiler catches everything) or the permissiveness of JavaScript (where everything just works somehow). Python occupies a different position: flexible at the surface, principled underneath.