Mid-level 4 min · March 05, 2026

Python Variables — NameError That Broke a Payment Pipeline

10% of payments crashed with NameError because total_charges was only defined in an if block.

N
Naren · Founder
Plain-English first. Then code. Then the interview question.
About
 ● Production Incident 🔎 Debug Guide
Quick Answer
  • Core concept: variables are named references to objects in memory, not containers
  • Assignment (=) binds a name to a value; the name points to the object
  • Dynamic typing: the same name can refer to different types over time
  • Performance: variable lookup is O(1) dictionary access in local/global scope
  • Production risk: NameError crashes pipelines when variable accessed before assignment
  • Biggest mistake: using = when you meant ==, causing a SyntaxError in conditions
Plain-English First

Think of a variable like a labelled box in a storage room. You write a name on the outside of the box (the variable name), then you put something inside it (the value). Whenever you need that thing later, you just call out the label and Python hands it back to you. The magic part? You can swap what's inside the box at any time without changing the label.

Every app you've ever used — from Instagram to Google Maps — is constantly juggling data. A username here, a distance there, a price, a temperature, a score. Without a way to temporarily hold that data while the program is running, none of it would be possible. Variables are the most fundamental building block of every program ever written, in every language that exists.

But here's the thing Python does differently: it doesn't force you to declare types. You just pick a name, assign a value, and Python figures out the rest. That simplicity is what makes Python the go-to language for beginners and a productivity powerhouse for pros. Get variables right, and everything else becomes easier to reason about.

What a Variable Actually Is (And Why Python Makes Them Effortless)

In most older languages like C or Java, you have to tell the computer upfront exactly what kind of data you're planning to store — a number, a word, a decimal — before you can even create the variable. Python throws that ceremony out the window. You just pick a name, use the equals sign, and put your value on the right. Python figures out the type automatically. That equals sign is called the assignment operator. It doesn't mean 'equal to' the way it does in maths — it means 'take the value on the right and store it under the name on the left'. So when you write player_score = 0, you're telling Python: create a box, label it player_score, and put the number 0 inside it. From that line forward, every time Python sees player_score, it looks inside that box and uses whatever's in there. This simplicity is intentional. Python's creator, Guido van Rossum, wanted the language to read almost like plain English, and variable assignment is the clearest example of that philosophy in action.

variable_basics.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# ── Creating variables ──────────────────────────────────────────
# On the left: the label (variable name)
# On the right: the value you want to store
# The = sign means ASSIGN, not 'is equal to'

player_name = "Alex"          # Storing a piece of text (called a string)
player_score = 0              # Storing a whole number (called an integer)
health_percentage = 100.0     # Storing a decimal number (called a float)
is_game_over = False          # Storing True or False (called a boolean)

# ── Reading variables ────────────────────────────────────────────
# Use print() to display the value stored inside any variable
print(player_name)            # Python looks inside the box labelled player_name
print(player_score)
print(health_percentage)
print(is_game_over)

# ── Updating a variable ──────────────────────────────────────────
# You can replace the value inside the box at any time
player_score = 50             # The old value (0) is gone; now it holds 50
print(player_score)           # Prints the NEW value
Output
Alex
0
100.0
False
50
Why This Matters:
Python determines the data type automatically based on the value you assign. This is called dynamic typing. You don't write 'string player_name' — you just write 'player_name = "Alex"' and Python knows it's text. This is one of the biggest reasons Python is the most beginner-friendly language in the world.
Production Insight
Dynamic typing reduces boilerplate but shifts type errors to runtime.
A simple typo like 'score = scpre 10' will raise NameError, not compile-time error.
Rule: test immediately after assigning any variable – don't wait until the code runs 50 lines later.
Key Takeaway
Dynamic typing does not mean no typing.
Every variable has a type – Python just figures it out when the line runs.
Always know what type your variable holds, especially when passing it to functions.

The Rules and Best Practices for Naming Python Variables

Python gives you a lot of freedom with variable names, but there are hard rules you cannot break and soft rules (conventions) that every professional follows. Breaking the hard rules causes an immediate crash. Breaking the conventions means your colleagues will quietly judge you. Hard rules: variable names can only contain letters, numbers, and underscores. They cannot start with a number. They cannot contain spaces. They cannot be one of Python's reserved keywords (like if, for, while, return — these mean something special to Python already). Case matters — age, Age, and AGE are three completely different variables in Python's eyes. The professional convention used across almost all Python code is called snake_case: all lowercase letters, with words separated by underscores. So instead of PlayerHealthPoints, you'd write player_health_points. This is defined in PEP 8 — Python's official style guide — and every serious Python codebase follows it. Good names are the cheapest form of documentation that exists. A variable named d tells you nothing. A variable named days_until_deadline tells you everything.

variable_naming.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# ── VALID variable names ─────────────────────────────────────────
user_age = 28                 # snake_case — the Python standard
city_of_birth = "Lagos"       # descriptive and readable
max_login_attempts = 5        # immediately clear what this stores
order_total_usd = 149.99      # units in the name — no ambiguity
_internal_counter = 0         # leading underscore = convention for internal use

# ── INVALID variable names (these will crash your program) ───────
# 2fast = True                # WRONG: cannot START with a number
# user-age = 28               # WRONG: hyphens are not allowed
# user age = 28               # WRONG: spaces are not allowed
# for = 5                     # WRONG: 'for' is a reserved Python keyword

# ── Case sensitivity demo ────────────────────────────────────────
temperature = 36.6            # This is one variable...
Temperature = 100.0           # ...and this is a COMPLETELY different variable
TEMPERATURE = -273.15         # ...and so is this — Python treats them separately

print(temperature)            # 36.6
print(Temperature)            # 100.0
print(TEMPERATURE)            # -273.15

# ── Name quality comparison ──────────────────────────────────────
d = 7                         # BAD  — what is d? Days? Dollars? Distance?
days_until_expiry = 7         # GOOD — crystal clear, zero guessing required
Output
36.6
100.0
-273.15
Watch Out:
Never name a variable after a Python built-in function like list, input, print, or type. Python will let you do it without crashing immediately — but you'll overwrite that built-in, and the next time you try to call print() or list() it will behave in completely unexpected ways. This is one of the sneakiest bugs beginners introduce.
Production Insight
A misnamed variable can silently overwrite a built-in, breaking code far from the assignment.
When you shadow 'list', calls to list() later will fail with TypeError.
Rule: before naming, check if it's a built-in — your IDE will colour it differently.
Key Takeaway
A good variable name is the cheapest documentation.
Follow snake_case, avoid reserved words, and never reuse built-in names.
Your future self (and code reviewers) will thank you.

Multiple Assignment, Swapping Values, and Checking Types

Python has a few elegant shortcuts for working with variables that feel almost like magic when you first see them. Multiple assignment lets you create several variables on a single line. You can also assign the same value to multiple variables at once — useful when initialising a group of counters to zero. Then there's the crown jewel: swapping two variables. In most other languages, swapping the values of two variables requires a third temporary variable to act as a middleman. In Python, you do it in one line. Under the hood Python is still using a temporary location, but it hides that complexity from you entirely. Finally, Python gives you the type() function, which tells you exactly what kind of data a variable is currently holding. This is especially useful when you're debugging and something isn't behaving the way you expect — checking the type is often the fastest way to spot the problem.

advanced_assignment.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# ── Assigning multiple variables in one line ────────────────────
# Each name on the left pairs up with the matching value on the right
first_name, last_name, age = "Maria", "Santos", 31
print(first_name)             # Maria
print(last_name)              # Santos
print(age)                    # 31

# ── Assigning the same value to multiple variables at once ───────
red_lives = blue_lives = green_lives = 3   # All three teams start with 3 lives
print(red_lives, blue_lives, green_lives)  # 3 3 3

# ── Swapping two variables — the Python way ──────────────────────
team_a_score = 45
team_b_score = 72

print(f"Before swap — Team A: {team_a_score}, Team B: {team_b_score}")

# In one line, Python swaps the values with no temporary variable needed
team_a_score, team_b_score = team_b_score, team_a_score

print(f"After swap  — Team A: {team_a_score}, Team B: {team_b_score}")

# ── Checking what type of data a variable holds ──────────────────
product_name = "Wireless Keyboard"   # text
product_price = 49.99                # decimal
units_in_stock = 200                 # whole number
is_available = True                  # boolean

print(type(product_name))            # what type is this variable?
print(type(product_price))
print(type(units_in_stock))
print(type(is_available))
Output
Maria
Santos
31
3 3 3
Before swap — Team A: 45, Team B: 72
After swap — Team A: 72, Team B: 45
<class 'str'>
<class 'float'>
<class 'int'>
<class 'bool'>
Pro Tip:
The f-string syntax you see in the swap example — f"Team A: {team_a_score}" — is the modern, preferred way to embed variable values directly inside strings. The f before the opening quote tells Python to look for anything inside curly braces and replace it with the variable's actual value. It's cleaner and faster than joining strings with + signs.
Production Insight
Multiple assignment on one line can cause order-dependent bugs if the right side mutates shared state.
Example: a, b = some_list, some_list.pop() — the pop happens during the tuple construction, not after.
Rule: when using multiple assignment, ensure right-hand expressions are independent and side-effect free.
Key Takeaway
Python's tuple packing and unpacking makes multiple assignment elegant.
One-line swaps avoid temporary variables but don't sacrifice readability.
Use type() early when debugging unexpected behaviour — it reveals most type mismatches quickly.

Variable Scope: Local vs Global – and What Python Actually Does in Memory

Where you create a variable determines how long it lives and who can see it. Variables defined inside a function are local — they exist only while that function runs. Variables defined at the top level of a script are global — they exist for the entire program. Python resolves names by searching in this order: local, enclosing function, global, built-in (LEGB rule). This matters because you can accidentally shadow a global variable inside a function by assigning to it, which creates a new local instead of modifying the global. To explicitly modify a global from inside a function, you use the global keyword. Objects themselves can be mutable (like lists) even when assigned to a global name — so you can modify the list's contents without using global. Memory-wise, each variable name is a key in a dictionary: locals() for local scope, globals() for global scope. This dictionary lookup is fast but not instant — Python caches local variables in a dedicated array for faster access.

variable_scope.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# ── Global variable ────────────────────────────────────────────
user_count = 0                # This exists for the entire program

# ── Local variable inside a function ─────────────────────────────
def process_user(name):
    local_greeting = f"Hello, {name}"   # local_greeting only exists inside this function
    user_count = 1                        # This creates a LOCAL variable, does NOT modify global
    print(local_greeting)
    print(f"Inside function, user_count = {user_count}")

process_user("Alice")
print(f"Outside function, user_count = {user_count}")  # Still 0!

# ── Using global keyword to modify the global ────────────────────
def increment_user_count():
    global user_count                    # Tell Python: use the global user_count
    user_count += 1

increment_user_count()
increment_user_count()
print(f"After global increment: {user_count}")  # 2

# ── Modifying a mutable global without global keyword ────────────
active_users = ["Bob"]          # global list
def add_user(name):
    active_users.append(name)   # No global keyword needed because we're modifying, not reassigning

add_user("Charlie")
print(active_users)             # ['Bob', 'Charlie']
Output
Hello, Alice
Inside function, user_count = 1
Outside function, user_count = 0
After global increment: 2
['Bob', 'Charlie']
Scope Mental Model
  • Inner circle: local function variables (most specific, looked up first)
  • Next circle: enclosing function variables (for nested functions)
  • Next circle: global module-level variables
  • Outer circle: Python built-ins like print, len, range
  • Python circles inward: LEGB rule saves lookup time by starting from the centre
Production Insight
The global keyword is often a code smell – it makes function behaviour depend on external state.
In large codebases, unexpected global modifications cause hard-to-reproduce bugs across modules.
Prefer passing values as function arguments and returning results instead of relying on globals.
Key Takeaway
Variables are scoped: local inside functions, global at module level.
Assigning to a variable inside a function creates a local unless you declare 'global'.
Modifying an object (like list.append) does not require global – only reassigning the name does.

Constants in Python – The Convention That Replaces the Keyword

Unlike many languages, Python doesn't have built-in constant declarations — no const or final keyword. Instead, the Python community follows a naming convention: constants are written in ALL_CAPS with underscores separating words. This signals to other developers: 'this value should not change.' The interpreter won't stop you from reassigning a constant — it's purely a convention. Violations are caught in code review, not by the compiler. This approach works surprisingly well in practice because Python trusts developers to follow the convention. For truly immutable values, you can use tuples or namedtuples (if they contain only immutable elements). When you see PI = 3.14159 at the top of a module, you know it's a constant. If someone later writes PI = 4, the code will still run, but your colleagues will have strong words with you.

constants.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# ── Constants are named using ALL_CAPS ───────────────────────────
MAX_RETRIES = 3
DEFAULT_TIMEOUT = 30
PI = 3.141592653589793
SUPPORTED_REGIONS = ("us-east", "eu-west", "ap-southeast")

# ── Python does NOT prevent reassignment — use with care ─────────
# MAX_RETRIES = 5   # This would violate the convention, but Python allows it

# ── Using constants keeps business logic clear ───────────────────
import time
def fetch_data(url, retries=MAX_RETRIES):
    for attempt in range(retries):
        try:
            response = make_request(url)
            return response
        except ConnectionError:
            if attempt == retries - 1:
                raise
            time.sleep(DEFAULT_TIMEOUT)

# ── Constants grouped in a dedicated module are best practice ────
# config.py
#   MAX_CONNECTIONS = 100
#   DATABASE_HOST = "db.prod.example.com"
Output
(no output — module definition)
Watch Out:
Constants defined with mutable objects (like a list) can be modified even if you don't reassign the name. For example, SUPPORTED_REGIONS.append('new-region') will modify the tuple… wait, tuples are immutable – but if you used a list, you could accidentally change its contents. Always use immutable types for constants if you want them to stay constant.
Production Insight
Relying on naming convention alone means a careless developer can mutate a constant, causing subtle bugs.
A common pattern is to enforce immutability by using namedtuple or dataclass(frozen=True).
For true safety in large teams, consider a static type checker like mypy with Final type annotation.
Key Takeaway
Python constants are a convention, not a language feature.
Use ALL_CAPS names and never reassign them – enforce in code reviews.
For immutable groups, use tuples or frozen dataclasses, never lists.
● Production incidentPOST-MORTEMseverity: high

The NameError That Took Down a Payment Pipeline

Symptom
The payment pipeline would start processing orders, then crash with a NameError: name 'total_charges' is not defined after approximately 10% of transactions.
Assumption
The developer assumed that total_charges was implicitly initialised to 0 because it was referenced inside an accumulating loop. They believed Python would create the variable automatically upon first assignment in the loop body.
Root cause
The variable total_charges was assigned inside an if block that only executed for premium orders. For standard orders, the if block was skipped, leaving the variable never defined. When the code later tried to log total_charges, Python raised a NameError.
Fix
Initialise total_charges = 0 at the start of each order loop, before any conditional logic. This guarantees the variable always exists regardless of the order type.
Key lesson
  • Never assume a variable will be defined by a code path you can't guarantee executes.
  • Always initialise variables to a sensible default (0, 0.0, '', or None) before branches or loops.
  • Add defensive checks: if 'total_charges' not in locals(): total_charges = 0
Production debug guideQuick reference for diagnosing common variable runtime errors4 entries
Symptom · 01
NameError: name 'x' is not defined
Fix
Check if the variable was assigned before this line. Look for typos in the name. Ensure the variable is in scope (local vs global). Use print(dir()) to list local names.
Symptom · 02
UnboundLocalError: local variable 'x' referenced before assignment
Fix
You have a global variable with the same name and assigned to it inside a function without global declaration. Either add 'global x' inside the function or rename the local variable.
Symptom · 03
TypeError: unsupported operand type(s) for +: 'int' and 'str'
Fix
One variable holds an unexpected type. Print type(variable_name) for all operands. Check if any variable was reassigned to a different type earlier in the flow.
Symptom · 04
AttributeError: 'int' object has no attribute 'append'
Fix
You assigned an integer to a variable originally intended to be a list. Check for unintended reassignment, e.g., my_list = some_function() that returned an int.
★ Quick Debug Cheat Sheet: Variable IssuesCommands to diagnose variable-related runtime errors fast
Variable not defined
Immediate action
Check the exact name and scope
Commands
print(dir())
print('variable_name' in locals() or 'variable_name' in globals())
Fix now
Assign a default value before usage or define the variable earlier
Variable has wrong type+
Immediate action
Print type of the variable
Commands
print(type(variable_name))
print(repr(variable_name))
Fix now
Convert with int(), str(), float() or fix the upstream assignment
Changed variable unexpectedly+
Immediate action
Find where it was last assigned
Commands
import pdb; pdb.set_trace()
!print(variable_name)
Fix now
Add watch or breakpoint at each assignment site
Python vs Java/C++ Variables
AspectPython VariablesVariables in Java / C++
Type declaration required?No — Python infers it automaticallyYes — you must write int age or String name
Can the type change after assignment?Yes — assign a new type freelyNo — the type is locked at declaration
Syntax to create a variableage = 25int age = 25;
Semicolon at end of line?Never neededRequired in Java/C++
Check variable type at runtime?type(variable_name)Not straightforwardly possible (limited reflection)
Swap two variables elegantly?a, b = b, a (one line)Requires a temporary third variable
Constant declaration?Convention: MAX_SIZE = 100 (no enforcement)final int MAX_SIZE = 100; (enforced)
Scope of name resolution?LEGB: Local, Enclosing, Global, Built-inBlock-level in Java; function-level in C++

Key takeaways

1
A Python variable is a named label pointing to a value in memory
not a fixed container. The label can be pointed at a completely different value (even a different type) at any time.
2
Python's single equals sign = means assignment (store this value), not equality. Use == when you want to compare two values.
3
Naming matters more than you think
player_score communicates intent instantly; p does not. Every variable name is a micro-comment about what the code does.
4
type() is your debugging best friend
when a variable isn't behaving as expected, call type() on it immediately. Nine times out of ten it's storing '42' (a string) instead of 42 (an integer).
5
Scope matters
variables defined inside functions are local by default. Use global only when absolutely necessary — it couples functions to external state.
6
Python constants are a convention, not a language feature. Name them in ALL_CAPS and never reassign
rely on code review for enforcement.

Common mistakes to avoid

3 patterns
×

Using a variable before assigning a value

Symptom
Python throws NameError: name 'total_price' is not defined — the program crashes.
Fix
Always assign a value before you try to use a variable. If you don't know the final value yet, initialise it to a sensible default like 0, 0.0, '', False, or [].
×

Confusing = (assignment) with == (comparison)

Symptom
Writing if user_age = 18 instead of if user_age == 18 causes a SyntaxError immediately.
Fix
Remember the rule — one equals sign stores a value, two equals signs compares two values. Read = as 'becomes' and == as 'is the same as'.
×

Shadowing a built-in by naming your variable input, list, print, or type

Symptom
Python does NOT warn you, but the built-in stops working for the rest of the file. Calling print() later gives TypeError: 'str' object is not callable.
Fix
Never use Python's built-in names as variable names. If you're unsure whether a name is reserved, type it in your editor; most editors will highlight it in a different colour to warn you.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01JUNIOR
What is the difference between a variable and a value in Python? Can you...
Q02JUNIOR
Python is described as dynamically typed. What does that mean, and how d...
Q03SENIOR
What happens in memory when you reassign a Python variable — for example...
Q01 of 03JUNIOR

What is the difference between a variable and a value in Python? Can you give a concrete example?

ANSWER
A variable is a name that refers to a value. The value is the actual data stored in memory. For example, in x = 10, x is the variable (a reference), and 10 is the integer value. The variable points to the value, not the other way around. If you reassign x = 'hello', the name x now points to a string, but the integer 10 might still exist in memory (if referenced elsewhere) or be garbage collected.
FAQ · 4 QUESTIONS

Frequently Asked Questions

01
Do I need to declare the type of a variable in Python?
02
Can a Python variable change its type after it's been created?
03
What is the difference between a local variable and a global variable in Python?
04
How do I create a constant in Python?
🔥

That's Python Basics. Mark it forged?

4 min read · try the examples if you haven't

Previous
Python Data Types
4 / 17 · Python Basics
Next
Operators in Python