The typeerror: ‘int’ object is not subscriptable in Python occurs when you try to access an element of an integer using index notation, such as `my_number[0]`. This error happens because integers are single, indivisible values and do not contain a sequence of items that can be “subscribed” or indexed into. It usually means a variable you expected to be a list, tuple, or string is mistakenly holding an integer value, which you cannot slice or access by position.
Key Benefits at a Glance
- Fix Bugs Faster: Quickly identify the root cause of the error, allowing you to resolve code-breaking issues and save valuable debugging time.
- Write Reliable Code: Learn to validate variable types before accessing them, which makes your programs more robust and less prone to unexpected crashes.
- Improve Coding Habits: Develop the practice of inspecting variable contents and anticipating data type mismatches, a key skill for professional developers.
- Prevent Future Errors: Understand the fundamental difference between subscriptable data types (like lists) and non-subscriptable types (like integers) to avoid similar issues.
- Increase Code Clarity: By correctly handling data types, your code becomes more readable and easier for you or others to maintain and debug in the long run.
Purpose of this guide
This guide is for Python developers, especially beginners, who have encountered the ‘int’ object is not subscriptable error and need a clear solution. It solves the frustrating problem of your script failing because it attempts to treat a number like a list or string. Here, you will learn how to diagnose the issue by checking variable types, understand common scenarios where this occurs (like with function return values or loops), and apply step-by-step fixes. Following this guide will help you quickly resolve the current error and build defensive coding skills to prevent it from happening again.
Introduction
Nothing quite matches the frustration of staring at your screen when Python throws a TypeError: 'int' object is not subscriptable at you. I've been there countless times during my years of Python development, and I know that sinking feeling when code that should work simply doesn't. This error has a way of appearing at the worst possible moments, often in code that looks perfectly reasonable at first glance.
After encountering this error hundreds of times across different projects, I've developed a systematic approach to both understanding and preventing it. This guide combines theoretical knowledge with practical solutions drawn from real debugging sessions and production environments. Whether you're a beginner hitting this error for the first time or an experienced developer looking for better prevention strategies, I'll walk you through everything you need to know to master this common Python pitfall.
What this error means and why it occurs
The TypeError: 'int' object is not subscriptable occurs when you attempt to use square bracket notation on an integer, which Python doesn't allow. Understanding what "subscriptable" means is crucial to grasping this error. In Python's type system, subscriptable objects are those that support indexing operations using square brackets.
“The error **’int’ object is not subscriptable** occurs when you attempt to use indexing or slicing on an integer, a data type that doesn’t support these operations.”
— GeeksforGeeks, July 2025
Source link
When I first encountered this error years ago, I was trying to access individual digits of a number using my_number[0]. The confusion stemmed from thinking about numbers like strings, where each position could be accessed individually. However, integers in Python are atomic values that don't support item access.
The fundamental issue lies in Python's type system design. Integers represent single numerical values, not collections of items. When you write 42[0], Python interprets this as an attempt to get the first item from the number 42, which doesn't make conceptual sense.
- Lists: my_list[0] ✓
- Strings: my_string[1] ✓
- Dictionaries: my_dict[‘key’] ✓
- Tuples: my_tuple[2] ✓
- Integers: my_int[0] ✗
- Floats: my_float[1] ✗
- Booleans: my_bool[0] ✗
- None: my_none[0] ✗
This error deepens your understanding of Python's type system by highlighting the distinction between scalar types (like integers) and collection types (like lists and strings). Recognizing this difference helps you write more predictable code and catch potential type issues before they become runtime errors.
Common scenarios where I encounter this error
Through years of Python development, I've noticed this error follows predictable patterns. Understanding these common scenarios helps you recognize the warning signs before the error occurs. Most instances fall into three main categories: variable reassignment issues, function return type mismatches, and misunderstanding of integer immutability.
“The Python error ‘TypeError: ‘int’ object is not subscriptable’ occurs when you try to treat an integer like a subscriptable object.”
— freeCodeCamp, Accessed 2025
Source link
In production environments, I've seen this error cause silent failures in data processing pipelines where type assumptions broke down under edge cases. The frequency of these scenarios reinforces that this isn't a beginner-only problem—even experienced developers encounter it when working with dynamic data or complex control flows.
Variable reassignment issues I've seen
Python's dynamic typing system allows variables to change types during execution, which creates opportunities for this error. I've debugged countless cases where a variable starts as a list or string but gets reassigned to an integer somewhere in the code flow.
A common mistake is reassigning a list variable to an integer (e.g., items = len(items)), then later trying to index it like items[0]. This confusion between data types is closely related to issues like “str object is not callable”, where variable naming conflicts overwrite built-in functions or methods—both stem from unintended type mutations.
The most common pattern involves loops where the iteration variable accidentally overwrites a collection variable. Here's a typical example I encountered while debugging a colleague's data processing script:
# Problematic code
data = [1, 2, 3, 4, 5]
for data in range(len(data)): # Overwrites the list!
print(f"Processing item {data}")
# Later in the code...
first_item = data[0] # TypeError: 'int' object is not subscriptable
The corrected version preserves the original variable:
# Fixed code
data = [1, 2, 3, 4, 5]
for index in range(len(data)):
print(f"Processing item {data[index]}")
# Now this works
first_item = data[0]
Another pattern occurs in conditional statements where different branches assign different types to the same variable. I once spent an hour debugging code where a variable was assigned a list in the main path but an integer in an error handling branch. When the error condition triggered, later code expecting a list would fail with this error.
Python's dynamic typing makes this error common but detectable with proper variable naming conventions and type awareness. The key insight is that variable reassignment can happen far from where the error manifests, requiring systematic tracing of variable assignments.
Function return value mismatches in my code
Functions that sometimes return subscriptable objects and other times return integers create subtle bugs that often surface in larger codebases. I encountered a particularly challenging case in a data validation library where a function returned a list of errors when validation failed, but returned the count of processed items (an integer) when validation succeeded.
# Problematic function with inconsistent return types
def validate_data(items):
errors = []
processed_count = 0
for item in items:
if not is_valid(item):
errors.append(f"Invalid item: {item}")
else:
processed_count += 1
if errors:
return errors # Returns list
else:
return processed_count # Returns int
# Usage that sometimes fails
result = validate_data(my_data)
first_issue = result[0] # Works with errors, fails with count
The improved version maintains consistent return types:
# Improved function with consistent return structure
def validate_data(items):
errors = []
processed_count = 0
for item in items:
if not is_valid(item):
errors.append(f"Invalid item: {item}")
else:
processed_count += 1
return {
'errors': errors,
'processed_count': processed_count,
'success': len(errors) == 0
}
# Usage that always works
result = validate_data(my_data)
if result['errors']:
first_issue = result['errors'][0]
This pattern becomes more common as projects scale because function contracts become less clear when multiple developers contribute code. The debugging challenge lies in the temporal separation between the function call and the subscripting attempt, making the connection non-obvious.
Why integer immutability matters
The immutable nature of integers contributes fundamentally to this error. Understanding immutability helps explain why integers cannot be accessed with subscript notation and distinguishes them from other immutable types that do support subscripting.
Integers are immutable, meaning their value cannot be changed after creation. However, immutability alone doesn't determine subscriptability. Strings and tuples are also immutable but support indexing because they represent sequences of items rather than single atomic values.
| Type | Mutable | Subscriptable | Example |
|---|---|---|---|
| Integer | No | No | 42 |
| String | No | Yes | ‘hello'[0] |
| List | Yes | Yes | [1,2,3][0] |
| Tuple | No | Yes | (1,2,3)[0] |
| Dictionary | Yes | Yes | {‘a’:1}[‘a’] |
This understanding improved my coding practices by making me more conscious of when I'm working with atomic values versus collections. When I need to access individual components of a number, I now explicitly convert to a string first, making the intent clear and avoiding accidental subscripting attempts.
The key insight is that subscriptability depends on whether a type represents a collection of items rather than its mutability. This distinction helps predict which operations are valid for different data types and guides design decisions about data representation.
My step by step debugging approach
When facing this error, I follow a systematic debugging methodology that saves countless hours compared to random trial and error. The fundamental question driving my approach is: "What variable did I expect to be subscriptable but is actually an integer?"
- Identify the exact line causing the error from traceback
- Check what variable you’re trying to subscript
- Print the variable’s type using type(variable_name)
- Print the variable’s value using print(repr(variable_name))
- Trace back where this variable was last assigned
- Verify the expected vs actual data flow
This systematic approach prevents the frustration of randomly changing code without understanding the root cause. I developed this methodology after years of inefficient debugging sessions where I would modify code based on guesses rather than evidence.
The key is methodical investigation rather than assumptions. Often, the variable causing the error isn't the one you initially suspect, especially in complex functions with multiple assignments or when working with function return values that might vary based on input conditions.
Print debugging techniques I use
Print debugging remains my go-to first response for this error type because it's fast, requires no special tools, and provides immediate insight into variable states. The key is strategic placement and informative output formatting.
# Strategic print statement placement
def process_items(data):
print(f"Input data: {repr(data)} (type: {type(data)})") # Entry point
if isinstance(data, list):
result = data[0]
print(f"Extracted first item: {repr(result)} (type: {type(result)})")
else:
result = len(str(data)) # Convert to string first
print(f"Calculated length: {result} (type: {type(result)})")
print(f"Returning: {repr(result)} (type: {type(result)})") # Exit point
return result
- Use f’Variable {var_name} is type {type(var_name)} with value {repr(var_name)}’
- Add print statements before and after suspected assignment operations
- Use print(‘DEBUG: entering function_name’) at function entry points
- Print function return values: print(f’Function returned: {result} (type: {type(result)})’)
- Use descriptive labels: print(‘BEFORE LOOP:’, my_var) and print(‘AFTER LOOP:’, my_var)
The repr() function is particularly valuable because it shows the exact representation of values, including quote marks for strings and precise formatting for other types. This helps distinguish between '42' (string) and 42 (integer) when debugging type-related issues.
I've found that simple print debugging often solves this error faster than setting up sophisticated debugging tools, especially for quick scripts or when working in environments where debugger setup is complicated.
How I use Python's debugging tools
For more complex scenarios, Python's built-in debugging tools provide powerful capabilities for inspecting variable states and execution flow. The pdb debugger excels at step-by-step execution analysis when print debugging becomes unwieldy.
import pdb
def complex_processing(data):
pdb.set_trace() # Debugger entry point
# Interactive commands useful for this error:
# (Pdb) type(data)
# (Pdb) repr(data)
# (Pdb) hasattr(data, '__getitem__')
# (Pdb) n # Next line
# (Pdb) s # Step into function
result = process_data_somehow(data)
return result[0] # Potential error location
| Method | Setup Time | Best For | Learning Curve |
|---|---|---|---|
| Print Statements | Instant | Quick fixes, small scripts | None |
| pdb | Minimal | Complex logic, step-through debugging | Low |
| IDE Debugger | Medium | Large projects, visual debugging | Medium |
| Logging | Medium | Production code, persistent debugging | Medium |
IDE debuggers in VS Code and PyCharm excel for this error type because they provide visual variable inspection and allow setting conditional breakpoints that trigger only when specific type conditions are met. I often set breakpoints that activate when type(variable) == int to catch unexpected integer assignments.
The progression from print debugging to sophisticated tools depends on project complexity and debugging time investment. For one-off scripts, print statements suffice, but for recurring issues in larger codebases, investing in proper debugging tool mastery pays dividends.
Error tracking tools I recommend for production
Production environments require different debugging approaches since direct code modification and print statements aren't feasible. Error tracking tools become essential for capturing and diagnosing this error when it occurs in live systems.
Tools like Sentry and Rollbar have saved me countless hours by providing detailed context about when and where this error occurs in production. They capture the full stack trace, user context, and environment details that help reproduce issues locally.
- Real-time error notifications with full stack traces
- Error grouping and frequency tracking
- User context and environment details
- Integration with deployment tracking
- Custom error tagging and filtering
I once caught a production bug where a third-party API occasionally returned integer status codes instead of expected data objects, causing this error in downstream processing. The error tracking tool showed the pattern occurred only with specific API endpoints and provided the exact request parameters that triggered the issue.
Setting up error tracking involves integrating the monitoring SDK into your application and configuring appropriate alert thresholds. The investment pays off by providing visibility into errors that only manifest under specific production conditions or user interactions.
My practical solutions and prevention strategies
Over years of encountering this error, I've developed a prevention-focused approach that emphasizes catching potential issues before they become runtime errors. These strategies improved my overall code quality beyond just avoiding this specific error type.
The core philosophy shifts from reactive debugging to proactive design. By implementing type checking practices, improving code structure, and understanding when type conversion is appropriate, you can eliminate entire classes of type-related errors from your codebase.
Type checking practices I follow
Verifying types before attempting operations forms the foundation of preventing this error. I use a combination of runtime checks and static analysis techniques depending on the project context and requirements.
# Type checking before subscripting
def safe_get_first(data):
# Runtime type checking
if isinstance(data, (list, tuple, str)):
return data[0] if data else None
elif hasattr(data, '__getitem__'):
return data[0]
else:
raise TypeError(f"Expected subscriptable type, got {type(data)}")
# With type hints for static analysis
from typing import Union, List, Optional
def get_first_item(data: Union[List, str]) -> Optional[str]:
if isinstance(data, (list, str)) and len(data) > 0:
return data[0]
return None
- DO: Use isinstance(var, (list, str, dict)) before subscripting
- DO: Add type hints to function parameters and returns
- DO: Use hasattr(obj, ‘__getitem__’) to check subscriptability
- DON’T: Use type(var) == list (use isinstance instead)
- DON’T: Assume function return types without verification
- DON’T: Skip type checks in frequently called functions
My type checking approach has evolved with Python's typing capabilities. For small scripts, simple isinstance() checks suffice. For larger applications, I use comprehensive type hints with tools like mypy for static analysis, catching type issues before runtime.
The Python tutorial explains subscriptable objects like lists and strings support indexing with square brackets. Common fixes include converting integers to strings using str() before indexing, or checking types with isinstance() to avoid the error.
Code structure improvements I've implemented
Higher-level architectural patterns naturally prevent this error by promoting type consistency and clarity. These structural improvements focus on design principles rather than point fixes.
# Problematic function design with mixed return types
def get_user_data(user_id):
if user_id < 0:
return -1 # Error code (integer)
user = database.get_user(user_id)
if not user:
return 0 # Not found (integer)
return user.to_dict() # Success (dictionary)
# Improved version with consistent return structure
def get_user_data(user_id):
if user_id < 0:
return {"success": False, "error": "Invalid user ID", "data": None}
user = database.get_user(user_id)
if not user:
return {"success": False, "error": "User not found", "data": None}
return {"success": True, "error": None, "data": user.to_dict()}
The architectural thinking that prevents entire classes of errors includes designing consistent return types, using explicit error handling patterns, and establishing clear type contracts between functions. This approach eliminates ambiguity about what types functions return under different conditions.
I've found that time invested in structural improvements pays dividends across the entire codebase, preventing not just this error but many other type-related issues. The key is thinking about type flow at the design level rather than fixing individual instances reactively.
When and how I convert to subscriptable types
Sometimes converting integers to subscriptable types like strings is the appropriate solution, particularly when you need access to individual digits or characters. However, this decision requires careful consideration of whether conversion fits your data model.
If you need to make an integer subscriptable (though rarely advisable), you might wrap it in a list or tuple. But more often, you should trace back to where the type changed unexpectedly—similar to debugging “cannot unpack non-iterable NoneType” errors, where a function returns None instead of an iterable due to a missing return statement.
- Determine if you need individual digits or characters
- Consider if str(integer) conversion makes semantic sense
- Evaluate if list(str(integer)) provides needed flexibility
- Check if the conversion fits your data model
- Test edge cases like negative numbers and zero
- Document why conversion was chosen over redesign
# Converting integers for digit access
number = 12345
# Method 1: Convert to string for character access
digits_as_strings = str(number)
first_digit = digits_as_strings[0] # '1'
# Method 2: Convert to list of digit integers
digits_as_ints = [int(d) for d in str(number)]
first_digit = digits_as_ints[0] # 1
# Method 3: Mathematical approach (sometimes better)
def get_first_digit(num):
return int(str(abs(num))[0])
The decision to convert types versus redesign the solution depends on the specific use case. For digit manipulation in mathematical contexts, conversion often makes sense. For other scenarios, the need to subscript an integer might indicate a design issue where a different data structure would be more appropriate.
I've learned to document the reasoning behind type conversions because future maintainers (including myself) need to understand why the conversion was necessary and whether it remains the best approach as requirements evolve.
Real world examples from my experience
These authentic case studies from my development work demonstrate how this error manifests in real projects and the debugging process I used to resolve each issue. Each example includes the full context, error manifestation, and lessons learned.
Case Study 1: Data Processing Pipeline Error
I encountered this error in a financial data processing system where CSV parsing occasionally returned integers instead of expected string lists. The error manifested sporadically, making it particularly challenging to debug.
# Original problematic code
def process_transaction_data(csv_row):
# csv_row was expected to be a list of strings
# Sometimes csv_row was an integer (line number) due to parsing bug
transaction_id = csv_row[0] # TypeError when csv_row is int
amount = float(csv_row[2])
return {"id": transaction_id, "amount": amount}
# The actual CSV parsing had a bug:
def parse_csv_file(filename):
with open(filename, 'r') as file:
for line_num, line in enumerate(file):
if line.strip():
yield line.strip().split(',')
else:
yield line_num # Bug: yielding integer instead of empty list
The debugging process involved:
- Adding print statements to capture the exact type and value of
csv_row - Discovering that empty lines in CSV files caused the parser to yield line numbers
- Tracing the parser logic to understand when integers were returned
- Implementing proper empty line handling
Case Study 2: API Integration Type Mismatch
A REST API integration failed when the third-party service changed their response format under certain conditions, returning error codes as integers instead of data objects.
# Expected API response format
# Success: {"status": "ok", "items": ["item1", "item2", "item3"]}
# Our code assumed 'items' was always a list
def process_api_response(response_data):
items = response_data.get('items', [])
first_item = items[0] # Failed when items was an integer error code
return first_item
# The API sometimes returned:
# Error: {"status": "error", "items": 404} # Integer instead of list
- Always verify function return types match expectations
- Use consistent variable naming to avoid reassignment confusion
- Implement type guards in functions that handle multiple data types
- Add logging at critical type conversion points
- Test edge cases where functions might return unexpected types
Case Study 3: Loop Variable Shadowing
In a data analysis script, a loop variable accidentally shadowed a dataset variable, causing the error when subsequent code tried to access the dataset.
# Problematic code with variable shadowing
dataset = load_sensor_readings() # Returns list of readings
# Process each reading
for dataset in dataset: # Shadows the original dataset variable!
analyze_reading(dataset)
# Later code fails
summary_stats = calculate_stats(dataset[0]) # dataset is now an integer
The debugging process highlighted the importance of:
- Careful variable naming to avoid shadowing
- Using descriptive loop variable names
- Code review practices that catch these patterns
- Static analysis tools that warn about variable shadowing
These real-world examples demonstrate that this error often occurs at the intersection of different systems or under edge conditions that aren't immediately obvious during development. The key lesson is implementing robust type checking and maintaining awareness of variable scope and assignment patterns.
For deeper debugging, review error handling techniques.
Frequently Asked Questions
The error “TypeError: ‘int’ object is not subscriptable” occurs in Python when you try to access an element of an integer using subscript notation, like square brackets, which integers do not support. This happens because integers are scalar values, not sequences or collections like lists or strings that allow indexing. To avoid this, ensure the object you’re trying to subscript is of a subscriptable type.
To solve the “TypeError: ‘int’ object is not subscriptable,” first identify the variable causing the issue by checking where you’re using subscript notation and verify its type with type() function. If it’s unexpectedly an integer, trace back to see why it’s not the expected list, string, or other subscriptable type, and correct the code accordingly. Adding print statements or using a debugger can help pinpoint the mismatch quickly.
Common scenarios for the “TypeError: ‘int’ object is not subscriptable” include mistakenly treating the result of functions like len() or max() as subscriptable, when they return integers. Another frequent case is when a function intended to return a list or tuple returns an integer due to a bug or edge case. It can also happen with incorrect variable assignments, such as overwriting a list with an int value.
Integers in Python cannot be subscripted because they are immutable scalar objects that represent single numeric values, not collections of items. Unlike lists, tuples, or strings, which are sequences that support indexing to access individual elements, integers have no internal structure for subscription. This design ensures type safety and prevents logical errors in code.
If you need to access parts of an integer, such as its digits, convert it to a string first using str() and then use subscript notation on the string. For mathematical operations, use division, modulo, or logarithms to extract specific digits without subscription. Always ensure your data type matches the operation to prevent the “TypeError: ‘int’ object is not subscriptable.”

