Common Variable Mistakes in Python

Learn the most frequent Python variable mistakes beginners make, including accidental list sharing, name collisions, scope errors, and mutable default arguments.

5 min read

Every Python learner writes code that does not work the way they expected, and a large share of those early surprises come from python variable mistakes that are predictable once you understand how Python names and objects work. A list that seems to change on its own, a name that Python claims is not defined even though you can see it on the screen, a function that remembers values from a previous call: these are not bugs in Python but consequences of how variables, references, and scope rules interact. The good news is that once you understand the underlying mechanisms, every one of these mistakes becomes easy to spot and fix.

This article collects the most frequent variable mistakes and explains not just the fix for each one, but why it happens. If you have been learning from the series on Python variables and object references, you already have the mental model needed to understand these errors. The goal here is to connect that model to the specific symptoms you will see in your own code.

Mistake 1: Sharing a mutable object when you meant to copy it

This is the single most common variable mistake in Python. You create a list, assign it to a second variable to make what you think is a backup, modify the original, and discover that the backup changed too. The problem is that assignment never copies an object; it only creates another reference to the same object.

pythonpython
original = [1, 2, 3]
backup = original          # backup points to the SAME list
original.append(4)
print(backup)              # [1, 2, 3, 4], backup changed too

The fix is straightforward once you know the cause. For a list, calling .copy(), using the list() constructor, or taking a full slice with [:] all produce a new list object with the same elements. After that, modifying one will not affect the other. The same issue applies to dictionaries and sets, which also have their own .copy() methods. For nested structures where inner containers also need to be independent, the copy module's deepcopy function handles every level.

Mistake 2: Using built-in names as your own variable names

Python comes with many built-in functions and types available without any import: str, list, dict, int, float, print, input, len, max, min, sum, type, id, range, open, and dozens more. Every one of these is just a name in Python's built-in namespace. When you create your own variable with the same name, you shadow the built-in and lose access to the original function.

If you write list = [1, 2, 3], the name "list" no longer refers to the built-in type that creates lists; it refers to your specific list. Trying to call it as a function later with names = list("hello") fails with a TypeError because a list object cannot be called. The fix is to choose a more descriptive name: numbers = [1, 2, 3] avoids the collision entirely. The names most frequently shadowed by beginners are list, dict, str, id, type, sum, and input. Check the article on Python variable naming rules for a complete guide to choosing names that do not collide with the language.

Mistake 3: Using a variable outside its scope

Python determines where a variable is visible based on where it was assigned. A variable assigned inside a function body is local to that function and cannot be accessed from outside it. If you need a value from inside a function, return it and capture it in a variable in the caller's scope:

pythonpython
def calculate():
    result = 42
    return result
 
value = calculate()
print(value)    # 42, works because we captured the return value

The reverse problem is also common: trying to modify a global variable from inside a function without declaring it global. Python treats any name assigned inside a function as local unless told otherwise. If you genuinely need to modify a global, use the global keyword. In most cases, a better design is to pass values as arguments and return results instead of relying on shared global state.

Mistake 4: Mutable default arguments that persist between calls

When you define a function with a default argument value, Python evaluates that default exactly once, at function definition time, not each time the function is called. For immutable defaults like integers and strings, this does not matter. For mutable defaults like lists and dictionaries, the same object is shared across every call that does not provide an explicit argument.

pythonpython
def add_item(item, target=[]):
    target.append(item)
    return target
 
print(add_item("a"))   # ['a']
print(add_item("b"))   # ['a', 'b'], same list reused

Every call that omits the second argument appends to the same list, which accumulates values across calls. This is almost never intentional. The standard fix is to use None as the default and create the mutable object inside the function body. Write def add_item(item, target=None): and then check if target is None: target = [] before appending. This pattern appears in nearly every Python function signature that needs a mutable default, and you will recognize it immediately once you know to look for it.

Mistake 5: Confusing identity with equality

The double-equals operator checks whether two objects have the same value. The is keyword checks whether two variables point to the exact same object in memory. Beginners sometimes use is to compare values, which works for small integers due to an optimization called interning but fails unpredictably for larger numbers.

The fix is simple: reserve is for checking against None, where it is the idiomatic and correct choice, and use double-equals for all value comparisons. The behavior of is with integers is a CPython implementation detail and should never be relied on in code that might run on a different Python version or implementation. If you are comparing numbers, strings, or any other values for equality, double-equals is the right tool.

These five mistakes account for a large portion of the variable-related bugs that beginners encounter. Each one flows from the same underlying truth: Python variables are names that reference objects, and understanding when you are creating a new object versus sharing an existing one is the skill that separates confident Python programmers from confused ones.

Rune AI

Rune AI

Key Insights

  • Assigning a list with = creates a shared reference, not a copy; use .copy() for an independent list.
  • Never use str, list, dict, or other built-in names as your own variable names.
  • Variables defined inside a function are local and invisible to code outside the function.
  • Mutable default arguments are evaluated once at definition time; use None as the default instead.
  • A NameError usually means the variable is in the wrong scope or misspelled.
  • UnboundLocalError happens when you try to use a variable before assigning it inside a function.
RunePowered by Rune AI

Frequently Asked Questions

Why does my list change when I modify a copy I made with =?

Using = to assign a list to a new variable does not create a copy; it creates a second reference to the same list object. Both names point to the same list in memory, so modifying it through either name affects what both names see. Use .copy(), list(original), or a full slice original[:] to create a real independent copy.

Why does Python say my variable is not defined when I can see it in the code?

The most common cause is that the variable was defined inside a function body, which creates a local scope invisible to code outside that function. Another cause is a typo in the variable name. Check for spelling differences and confirm the variable is defined in a scope the current code can reach.

Why do default argument values in my function remember values between calls?

Default argument values are evaluated once, at function definition time, not each time the function is called. If the default is a mutable object like an empty list, the same list is reused across all calls that do not provide an explicit argument. Use None as the default and create the mutable object inside the function body instead.

Conclusion

Variable mistakes are a normal part of learning Python. Shared list references, naming collisions, and scope confusion all share the same root cause: misunderstanding how Python names point to objects and where those names are visible. Learn to recognize these patterns, and you will spend far less time debugging and far more time building.