Every Python programmer encounters data type mistakes, and the same handful of errors appear again and again across codebases, tutorials, and debugging sessions. The mutable default argument catches almost everyone who writes functions with list or dictionary parameters. The difference between the is operator and the double-equals operator causes subtle bugs that pass code review because the code looks correct at a glance. Implicit type conversions, truthiness assumptions, and unvalidated input all combine to create runtime errors that could have been prevented with a few simple habits. Learning these common Python data type mistakes now, before they appear in your own code, saves hours of frustration later.
This article draws together lessons from across the entire data types section. If you have read about immutable and mutable types, the is versus double-equals distinction, and type conversion functions, you already have the conceptual foundation. This article shows you how those concepts interact in practice when things go wrong, and more importantly, it gives you the patterns and mental checklists that prevent each mistake from happening in the first place.
Mistake 1: Mutable default arguments
The most infamous data type mistake in Python is using a mutable object as a default argument value. When Python defines a function, it evaluates the default argument expressions exactly once, at definition time, and stores the resulting objects. Every call to the function that omits that argument receives the same stored object, not a fresh copy. If the object is mutable and the function modifies it, the modification persists across calls.
The classic example is a function that collects items into a list. The programmer writes a function signature with an empty list as the default value, intending each caller to get its own empty list. Python creates one empty list when it reads the function definition. The first call appends an item to that list. The second call finds the list already populated with the first call's item and appends to it again. After three calls, the list contains three items instead of one per caller. This behavior is not a bug in Python. It is the documented and consistent result of default arguments being evaluated at definition time, but it violates the programmer's expectation so reliably that it has become the canonical cautionary tale of Python data types.
The fix is straightforward and universal: use None as the default value and create the mutable object inside the function body when the argument is None. None is immutable and safe. The None check runs on every call, so each caller gets a fresh mutable object. This pattern appears in the standard library, in every popular framework, and in the code of every experienced Python developer. Once you internalize it, you will never fall into the mutable default trap again.
def add_item(item, target=None):
if target is None:
target = []
target.append(item)
return target
print(add_item(1))
print(add_item(2))
print(add_item(3))Running this code prints [1], then [2], then [3]. Each call gets its own independent list because the None check creates a fresh empty list on every invocation that does not provide an explicit target. Had the default been the empty list itself, the output would have been [1], then [1, 2], then [1, 2, 3], which is almost certainly not what the programmer intended.
Mistake 2: Confusing identity with equality
Using the is operator when you meant to use double-equals, or vice versa, produces code that works correctly in some situations and fails mysteriously in others. The is operator checks whether two names point to the same object in memory. The double-equals operator checks whether two objects have the same value. For small integers, Python caches and reuses objects, so is accidentally works. For larger integers, it does not, and code that passed tests with small numbers breaks with larger ones.
The rule is simple and absolute. Use double-equals for comparing values like numbers, strings, lists, and dictionaries. Use is only for comparing against singletons like None or custom sentinel objects, or when you genuinely need to know whether two references point to the exact same object. If you are checking whether a function returned None, use an is None check. If you are checking whether a user's age is thirty, use double-equals. Confusing these two operators is especially dangerous with boolean values because True and False are singletons in Python, so is appears to work, but code that relies on this behavior is fragile and non-idiomatic.
Mistake 3: Overly broad falsiness checks
Python's truthiness rules are convenient but can hide bugs when you use a broad falsiness check where a specific type check is needed. The expression if not value catches all falsy values: None, zero, empty strings, empty lists, empty dictionaries, and False. If your function accepts zero as a valid value, checking if not value incorrectly treats zero the same as None. An age of zero for a newborn, a balance of zero for an empty bank account, or a count of zero for an empty inventory are all legitimate values that a broad falsiness check would reject.
The fix is to be specific about what you are checking for. Use if value is None when you need to detect missing data. Use if value == 0 when you need to detect zero specifically. Use if not value only when you genuinely want to catch every falsy value, which is rarer than most beginners assume. Being explicit about your checks makes your code's intent clearer and prevents edge cases from slipping through.
Mistake 4: Unvalidated type conversions
Calling int, float, or other constructor functions on data from external sources without validation is a common source of runtime errors. User input, file contents, API responses, and database fields can all contain values that fail to parse. A user might type "twelve" instead of 12. A CSV file might have an empty cell where a number is expected. An API might return a null value for a field that is normally numeric. Calling int on any of these inputs raises a ValueError that crashes the program if not caught.
The defensive pattern is to validate before converting or to wrap the conversion in a try-except block that provides a fallback. For predictable formats, validate with string methods like isdigit first. For unpredictable formats, catch the ValueError and either return a default value, log a warning, or re-raise with a more descriptive error message that includes the problematic input. This pattern appears in every production codebase that handles external data, and adopting it early prevents an entire category of crashes.
Mistake 5: Forgetting that bool is a subclass of int
In Python, bool is a subclass of int. The values True and False are literally the integers 1 and 0 with a different string representation and a different type name. This design choice means that booleans participate in arithmetic, comparisons, and any context that accepts integers. Adding True and True yields 2. Comparing True to 1 with double-equals returns True. Using isinstance to check whether a boolean value is an integer returns True because bool inherits from int.
This subclass relationship is usually harmless, but it can cause subtle bugs when you are checking types explicitly. If your code uses isinstance to dispatch between integers and other types, boolean values will match the integer branch. If you store a mixture of integers and booleans in a dictionary, the keys 1 and True will collide because they hash to the same value. The safest approach is to avoid mixing booleans and integers in the same collection or type-sensitive dispatch logic. When you must distinguish them, use type for an exact type check rather than isinstance, which respects the inheritance hierarchy.
Rune AI
Key Insights
- Never use a mutable object like [] or {} as a default argument; use None and create the object inside.
- Use is None, not == None, for checking missing values.
- Be explicit when checking for zero, empty strings, or None; broad falsiness checks hide bugs.
- Validate input before calling int(), float(), or other constructors that raise errors on invalid input.
- Remember that bool is a subclass of int, so True == 1 and False == 0 in numeric contexts.
Frequently Asked Questions
What is the most common Python data type mistake for beginners?
Why does my code treat 0, None, and empty strings the same way?
Why does int('3.14') raise a ValueError?
Conclusion
Data type mistakes are among the most common bugs in beginner Python code, but they are also among the most preventable. Mutable defaults, identity-versus-equality confusion, implicit falsiness that catches too much, and conversion errors from unvalidated input can all be avoided with a few simple rules. Write None-safe defaults, use is for None, validate before converting, and prefer specific checks over broad falsiness tests. These habits, once formed, will prevent hours of debugging across every Python project you work on.
More in this topic
Create and Assign Variables in Python
Learn every way to create and assign variables in Python, from basic single assignment to expressions, type hints, and practical patterns beginners use every day.
Python Variable Naming Rules
Learn Python's variable naming rules, including allowed characters, reserved keywords, PEP 8 conventions, and how to choose names that make your code readable.
Python Variables Explained
Understand what Python variables are, how they store and reference data, and why Python's variable model is different from other programming languages.