Static vs Dynamic Typing in Python: What Is the Difference?

Python is a dynamically typed language, which means variable types are resolved while the program is running rather than before it starts. Understanding what that means in practice — and how it compares to static typing — is foundational knowledge for every Python programmer.

When people say Python is dynamically typed, they are describing when and how the language decides what type a value is. This is not about performance or syntax style — it is a fundamental characteristic of how the language works. Contrast that with statically typed languages like Java or C++, where you must declare variable types explicitly before the program can compile. Each approach has real tradeoffs, and Python occupies an interesting middle ground: dynamic by nature, but with optional tooling that brings static type checking into the workflow.

What Is Dynamic Typing?

In a dynamically typed language, a variable does not have a fixed type. The type is associated with the value the variable holds, not with the variable itself. Python determines the type of a value at runtime — meaning when the code is actually executing — rather than ahead of time.

The practical result is that you never need to declare what type a variable will hold when you write Python code. You simply assign a value and Python figures out the type on its own.

Python
# Python infers type at runtime
score = 42           # int
name = "Alex"        # str
ratio = 3.14         # float
active = True        # bool

# You can even reassign a different type to the same variable
score = "forty-two"  # now it's a str — Python allows this

Notice that the last line reassigns score from an integer to a string. Python does not raise an error for this. The variable name is just a label that points to a value, and that value can be of any type at any time.

Note

Dynamic typing does not mean Python is untyped. Every value in Python has a type. You can verify this at any point by calling type(value). The difference is that the type lives with the value, not with the variable name.

Type errors in a dynamically typed language only surface when the offending line of code actually runs. If you write a function that tries to add a string and an integer together, Python will not complain until that function is called with those arguments. This is the central tradeoff of dynamic typing: flexibility at the cost of catching certain bugs later.

Python
def add_points(a, b):
    return a + b

# Works fine
print(add_points(10, 5))      # 15

# Type error — but Python only raises it at runtime, not before
print(add_points(10, "five")) # TypeError: unsupported operand type(s)

What Is Static Typing?

Static typing means that variable types are declared explicitly in the source code and are verified before the program runs. In languages like Java, C, C++, and Go, the compiler checks types as part of the build process. If a type mismatch exists anywhere in the code, the program will not compile, and you will see the error immediately.

Here is what a statically typed function looks like in Java, for comparison:

Java
// Java — types must be declared
int addPoints(int a, int b) {
    return a + b;
}

// This would cause a compile-time error in Java:
// addPoints(10, "five");  // incompatible types

The benefit here is clear: the compiler catches type errors before the code ever runs, which prevents an entire category of runtime bugs. The tradeoff is that you must write more explicit code and think about types upfront, even in situations where the types seem obvious.

Pro Tip

Static typing is especially valuable in large codebases with multiple developers. When types are declared and enforced, it becomes much harder to accidentally pass the wrong kind of data into a function — and IDEs can use that information to provide much more accurate autocomplete and refactoring support.

Side-by-Side Comparison

The table below captures the core differences between static and dynamic typing so you can see them in one place.

Characteristic Static Typing Dynamic Typing (Python default)
When types are checked Before the program runs (compile time) While the program runs (runtime)
Type declarations Required in source code Not required (optional via hints)
Type errors surface At build/compile time Only when the offending code runs
Variable reassignment to different type Not permitted Permitted
Common languages Java, C, C++, Go, Rust, Swift Python, JavaScript, Ruby, PHP
Verbosity Higher (more explicit code) Lower (less boilerplate)
IDE support / autocomplete Richer out of the box Improved when type hints are added

Type Hints in Python: Bringing Static Analysis to a Dynamic Language

Python introduced optional type hints in Python 3.5 through PEP 484. Type hints let you annotate variables, function parameters, and return values with expected types — without changing how Python runs your code. The interpreter still ignores these annotations at runtime. Their value comes from external tools and from making your code easier to read.

Python
# Without type hints
def greet(name):
    return "Hello, " + name

# With type hints — clearer intent, no change in behavior
def greet(name: str) -> str:
    return "Hello, " + name

# Variables can be annotated too
user_count: int = 0
username: str = "kandi"

The key thing to understand: adding : str or -> int to your Python code does not make Python statically typed. If you call greet(42) at runtime, Python will still execute the function and raise a TypeError when it tries to concatenate an integer with a string — the annotation does not stop that.

Using mypy for Static Analysis

Where type hints become genuinely powerful is when paired with a tool like mypy, a static type checker for Python. mypy reads your annotated code and reports type inconsistencies before the program runs, acting much like a compiler type check from static languages.

Python — example.py
# Install mypy
# pip install mypy

# example.py
def add_points(a: int, b: int) -> int:
    return a + b

result = add_points(10, "five")  # mypy will flag this
Terminal
# Run from the command line:
# mypy example.py

# mypy output:
# example.py:5: error: Argument 2 to "add_points" has incompatible
# type "str"; expected "int"  [arg-type]

mypy catches the problem on line 5 before the program has run a single line. This is the same protection offered by statically typed languages — applied selectively to Python code that has been annotated.

The typing Module for Complex Types

Python's built-in typing module provides annotations for more complex types. For Python 3.9 and later, many of these are available directly from built-in types (e.g., list[str] instead of List[str]). Both styles are widely seen in real codebases.

Python
from typing import List, Dict, Optional, Union

# A function that takes a list of strings and returns a dict
def build_index(words: List[str]) -> Dict[str, int]:
    return {word: i for i, word in enumerate(words)}

# Optional means the value can be str or None
def find_user(user_id: int) -> Optional[str]:
    users = {1: "Kandi", 2: "Jordan"}
    return users.get(user_id)

# Union means the parameter can be int or float
def format_score(score: Union[int, float]) -> str:
    return f"Score: {score:.2f}"

# Python 3.10+ shorthand for Union using |
def format_score_modern(score: int | float) -> str:
    return f"Score: {score:.2f}"
Watch Out

Type hints are not validated at runtime by the Python interpreter. A function annotated with -> int can still return a string at runtime without any error from Python itself. Static analysis tools like mypy catch these issues, but only if you run them. This is a common source of confusion for developers coming from Java or C++.

How To Add Type Hints to a Python Function

Adding type hints to your Python functions is a straightforward process. Here is a step-by-step walkthrough that takes you from an unannotated function to a fully type-checked one using mypy.

  1. Install mypy Open your terminal and install mypy so you have a static checker ready: pip install mypy
  2. Write a plain function without annotations Start with a normal Python function. Example: def add(a, b): return a + b — this works fine but gives tools no information about expected types.
  3. Add parameter type annotations Place a colon and the type after each parameter name. For example: def add(a: int, b: int). Use built-in types like int, str, float, bool, or import complex types from the typing module.
  4. Add the return type annotation After the closing parenthesis, add an arrow and the return type before the colon: def add(a: int, b: int) -> int:. If the function returns nothing, use -> None.
  5. Run mypy against your file From the command line, run mypy your_file.py. mypy reads the annotations and reports any type mismatches it finds before the code runs.
  6. Fix reported type errors Review the mypy output, correct any mismatches, and re-run until mypy reports no errors. Your code now benefits from pre-run type checking while remaining valid Python.

Here is a complete before-and-after example showing the full process:

Python — before & after
# BEFORE: no type hints
def get_username(user_id):
    users = {1: "Kandi", 2: "Jordan"}
    return users.get(user_id)

# AFTER: fully annotated
from typing import Optional

def get_username(user_id: int) -> Optional[str]:
    users = {1: "Kandi", 2: "Jordan"}
    return users.get(user_id)

# Calling it correctly
name = get_username(1)         # mypy: OK
bad  = get_username("one")     # mypy: error — expected int

Frequently Asked Questions

Is Python statically or dynamically typed?

Python is dynamically typed by default. Variable types are determined at runtime rather than at compile time, so you do not need to declare a type when creating a variable. You can optionally add type hints for documentation and tooling purposes, but the Python interpreter does not enforce them during execution.

What is the difference between static and dynamic typing?

In static typing, variable types are declared and checked before the program runs, usually at compile time, and type errors are caught early. In dynamic typing, types are resolved during execution. Python uses dynamic typing, which allows variables to hold any type of value and to change type throughout a program.

Do Python type hints make Python statically typed?

No. Python type hints are annotations added to your code as documentation and to support external tools like mypy. The Python interpreter itself does not enforce them at runtime. Adding type hints to Python code does not change the language's fundamental dynamic nature.

What is mypy and how does it relate to static typing in Python?

mypy is a static type checker for Python. When you annotate your Python code with type hints, you can run mypy as a separate tool to analyze your code before execution and report type errors. This brings some of the benefits of static typing to Python without changing how the Python interpreter works.

Should I use type hints in my Python code?

Type hints are generally recommended for larger projects, libraries, and any code shared with a team. They improve readability, enable better IDE autocomplete, and allow static analysis tools to catch bugs before runtime. For small personal scripts or quick prototypes, skipping them is perfectly reasonable.

Can a variable in Python change its type?

Yes. Because Python is dynamically typed, the same variable name can refer to different types of values at different points in a program. A variable that holds an integer can later hold a string. This is valid Python syntax, though reassigning a variable to a different type is generally considered poor style in production code.

Key Takeaways

  1. Python is dynamically typed: Types are resolved at runtime, not before the program runs. You never need to declare a type for a variable — Python determines it from the value assigned.
  2. Static typing checks types before execution: Languages like Java and C++ require type declarations and catch type mismatches at compile time, preventing an entire class of runtime bugs.
  3. Type hints are optional annotations, not enforcement: Adding type hints to Python code improves readability and enables tooling, but the Python interpreter does not enforce them. They are documentation first.
  4. mypy brings static analysis to Python: Running mypy on annotated Python code gives you pre-run type checking similar to what compiled languages provide, without changing Python's dynamic runtime behavior.
  5. Use type hints for any code meant to last: Annotating functions and variables pays off quickly in larger projects through better IDE support, easier refactoring, and fewer type-related bugs reaching production.

Whether you embrace Python's dynamic flexibility for rapid scripting or layer on type hints and mypy for production-grade safety, understanding the distinction between static and dynamic typing gives you the vocabulary and the tools to make that choice deliberately.