Fun with Learning Technology
LearnCoursesQuestionsTracksToolsNewsExplorePractice
Fun with Learning Technology

A new problem, explained clearly, every day.

Subscribe
Learn
  • Lessons
  • Topics
  • News
  • Tools
  • Courses
  • Career tracks
  • Everything
Site
  • About
  • Contact
  • Support
  • Privacy
  • Terms
Get the daily one

One email per new problem. No spam.

Request a tutorial

Requests shape what gets made next.

© 2026 Fun with Learning TechnologyRSS
Home›Courses›Python›Common Built-in Exceptions

File and Error Handling

Common Built-in Exceptions

Built-in exceptions are Python's mechanism for signaling that an operation cannot proceed due to runtime errors. Understanding these allows you to write defensive code that anticipates failure instead of crashing unexpectedly during production. You reach for exception handling whenever your program interacts with external data, hardware, or user input that might violate expected constraints.

SyntaxError and IndentationError

These are the most fundamental exceptions because they prevent the Python interpreter from even parsing your code. A SyntaxError occurs when the structural rules of the language are violated, such as missing a colon after a control flow statement or an unbalanced parenthesis. Unlike runtime exceptions, these are typically caught by the parser before the program execution begins. An IndentationError is a specialized subclass of SyntaxError that specifically flags improper nesting within blocks, which is crucial since Python relies on whitespace to define scopes. Because the interpreter must understand the logic flow to execute it, these errors act as a fail-fast mechanism. When you encounter these, it usually means your code lacks the formal structure required to define a valid scope. Always check the line number provided by the traceback, as the actual error often precedes the flagged line, such as a missing closing bracket on the line above.

# Example of a SyntaxError caused by a missing colon
# def greeting()  # Missing colon here causes SyntaxError
#     print("Hello")

# Example of IndentationError caused by misaligned block
def check_logic():
    x = 10
  if x > 5:  # Improper indentation for this scope
        return True

NameError and UnboundLocalError

A NameError is raised when your code attempts to access a variable or function name that has not been defined in the current local or global scope. This happens when you try to reference an object before it exists or if you make a typo in the identifier name. Python's scoping rules determine how it searches for names, checking the local function scope first, then enclosing scopes, then the global module scope, and finally built-ins. If the search reaches the top without finding a match, the exception is triggered. A related issue is the UnboundLocalError, which occurs specifically when you try to use a local variable before it has been assigned a value within the same scope, particularly when trying to modify a global variable from inside a function without explicit keyword declaration. Reasoning about these errors requires you to verify your variable lifecycles and ensure that names are resolved before they are utilized in expressions.

# Triggering a NameError by referencing an undefined variable
# print(my_secret_variable)  # Raises NameError if not defined

def scope_test():
    # UnboundLocalError occurs if you try to increment a variable
    # without setting it locally first while attempting to modify it.
    # value += 1  # Raises UnboundLocalError if value is not defined
    value = 5
    return value + 1

TypeError and ValueError

These exceptions occur when operations are performed on incompatible objects. A TypeError signifies that an operation is applied to an object of an inappropriate type, such as attempting to add an integer to a string. Python is strongly typed, meaning it does not implicitly coerce types in ways that could lead to ambiguous results, forcing the developer to be explicit. Conversely, a ValueError occurs when an operation receives an argument that has the correct type but an inappropriate value. For example, passing a negative number to a mathematical function that only supports positive domains, or calling .pop() on an empty list. Understanding these differences is vital for robust API design. When you encounter these, you must decide whether to coerce your data into the expected format or add validation logic to prevent invalid data from ever reaching the sensitive operation, thus maintaining data integrity across your application's logic.

# TypeError: attempting to concatenate string and integer
# result = "Score: " + 100 

# ValueError: math.sqrt cannot accept a negative number
import math
try:
    print(math.sqrt(-1))  # Raises ValueError
except ValueError as e:
    print(f"Caught expected error: {e}")

IndexError and KeyError

These exceptions signal attempts to access elements in sequences or mappings that do not exist. An IndexError occurs when you try to access a sequence (like a list or tuple) at an index that is outside the valid range of its length. Since sequences are indexed starting from zero, the maximum valid index is always length minus one. A KeyError is the equivalent for mappings, such as dictionaries, raised when you attempt to access a key that is not present in the dictionary. These errors are highly common in data processing tasks. To prevent them, you should either implement boundary checking before access or utilize safe retrieval methods. For dictionaries, using the .get() method allows you to specify a default value instead of letting the program crash, which is a powerful pattern for handling missing configuration data or incomplete input payloads during execution.

# IndexError: accessing an index that doesn't exist
items = [1, 2, 3]
# print(items[5])  # Raises IndexError

# KeyError: accessing a non-existent dictionary key
config = {"port": 8080}
# print(config["host"])  # Raises KeyError

# Safe access using .get()
print(config.get("host", "localhost"))  # Returns default value

AttributeError and ImportError

An AttributeError arises when you try to access or modify an attribute or method that does not exist on a given object. This often happens if you assume an object is of a certain class when it is actually a different type, or if you misspell a method name. Because Python is dynamic, checking attributes often involves inspecting the object's dictionary or using reflection. An ImportError, however, occurs when you attempt to load a module that cannot be found or when a specific name cannot be imported from a module. This is usually related to issues with the system path, missing dependencies, or circular imports. Resolving these requires verifying your environment setup and ensuring that the module structure matches your import statements. By mastering these common errors, you gain the ability to debug complex application states and ensure your code is resilient against unexpected runtime environmental variations.

# AttributeError: accessing a method that doesn't exist
my_list = [1, 2]
# my_list.append_value(3)  # AttributeError: list has no such method

# ImportError: attempting to import a non-existent library
try:
    import non_existent_library
except ImportError:
    print("Module could not be loaded.")

Key points

  • SyntaxError occurs when your code violates the structural rules of Python and prevents the script from starting.
  • NameError happens when you attempt to use an identifier that has not been defined in the current or global scope.
  • TypeError indicates that an operation was performed on an object of a type that does not support that action.
  • ValueError signals that a function received an argument of the correct type but an invalid, unusable value.
  • IndexError is raised when trying to access a list element using an index that is outside the defined sequence range.
  • KeyError occurs when attempting to retrieve a dictionary key that does not exist in the collection.
  • AttributeError is triggered when code tries to access a method or attribute that an object does not possess.
  • ImportError arises when the interpreter fails to locate a module or a specific function within a module.

Common mistakes

  • Mistake: Using a bare 'except:' clause. Why it's wrong: It catches absolutely everything, including system exits and keyboard interrupts, masking bugs you didn't intend to catch. Fix: Always catch specific exceptions, like 'except ValueError:' or 'except Exception:' if necessary.
  • Mistake: Checking for an error using 'if' statements instead of try-except. Why it's wrong: This is prone to race conditions (TOCTOU) where the state changes between the check and the action. Fix: Use the 'EAFP' (Easier to Ask for Forgiveness than Permission) principle by wrapping the dangerous code in a try-except block.
  • Mistake: Catching 'BaseException' instead of 'Exception'. Why it's wrong: 'BaseException' is the root of the exception hierarchy and includes system-level events like 'KeyboardInterrupt' and 'SystemExit'. Fix: Use 'Exception' to catch standard application-level errors while allowing system signals to propagate.
  • Mistake: Assuming a 'try-except-finally' block suppresses an error. Why it's wrong: The code will still crash unless an 'except' block handles the specific exception type raised. Fix: Explicitly define the exception types you expect to handle within the 'except' clauses.
  • Mistake: Putting too much code inside a single 'try' block. Why it's wrong: It makes it unclear which operation actually failed, potentially hiding unrelated bugs within the same block. Fix: Keep 'try' blocks narrow, covering only the specific operation that might raise an exception.

Interview questions

What is a SyntaxError and how does it differ from other exceptions in Python?

A SyntaxError occurs when the Python interpreter cannot parse your code because it violates the grammar rules of the language, such as missing a closing parenthesis or an incorrect indentation. Unlike other exceptions, this happens before the code actually executes. If you have a SyntaxError, your entire script fails to run at all. For example, writing 'print('hello'' without the closing parenthesis will trigger this error immediately during the parsing phase, preventing any line of code from being evaluated.

Can you explain the difference between an IndexError and a KeyError, and provide examples of when each occurs?

An IndexError and a KeyError are both container-related exceptions. An IndexError occurs when you try to access an element at an index that does not exist in a list or sequence, such as 'my_list[10]' when the list only has three items. A KeyError happens when you try to access a key that is missing in a dictionary, like 'my_dict['age']' when that key was never assigned. Understanding these helps you implement better input validation.

What is the purpose of a ValueError in Python, and how is it typically used in function definitions?

A ValueError is raised when a function receives an argument that has the right type but an inappropriate value. For example, if you call 'int('xyz')', Python knows you are passing a string, but the content cannot be converted into an integer, raising a ValueError. Developers use this to enforce logic: if a function expects a positive number for a calculation, you can manually 'raise ValueError('Input must be positive')' if the user provides a negative integer.

How does a NameError differ from a UnboundLocalError, and why does Python distinguish between them?

A NameError is raised when you try to use a variable name that hasn't been defined in the current scope, like calling 'x' before 'x = 5'. A UnboundLocalError is a specific subclass of NameError that happens when you try to modify a local variable before assigning it a value within a function. This often occurs when you try to perform 'x += 1' inside a function without first declaring 'global x' or 'nonlocal x'.

Compare using a try-except block versus using 'if-else' checks to handle potential exceptions like KeyError.

Using 'if-else' to check for existence is often called 'Look Before You Leap' (LBYL), while using 'try-except' is 'Easier to Ask for Forgiveness than Permission' (EAFP). LBYL checks keys using 'if key in my_dict' before accessing them. EAFP attempts the access directly inside a 'try' block and catches the KeyError. EAFP is generally preferred in Python for cleaner, more readable code, especially when dealing with race conditions where the state might change between the check and the access.

Explain the significance of the AttributeError and why it is commonly encountered when working with objects.

An AttributeError is raised when you attempt to access an attribute or method that does not exist for a given object. This happens frequently when you make assumptions about an object's type, such as trying to call '.append()' on a tuple, which is immutable and lacks that method. It highlights the importance of understanding the class structure of your data. You can proactively avoid this by using the 'hasattr(obj, 'attribute_name')' function to verify availability before attempting an operation.

All Python interview questions →

Check yourself

1. When should you use the 'IndexError' exception rather than 'KeyError'?

  • A.When accessing a value in a dictionary with a missing key
  • B.When accessing a sequence element with an out-of-range integer
  • C.When a function receives an argument of the wrong type
  • D.When a file operation fails to find a specified path
Show answer

B. When accessing a sequence element with an out-of-range integer
IndexError is for sequences like lists or strings. KeyError is for dictionaries. TypeError is for wrong types, and FileNotFoundError is for missing files.

2. Which of the following is the most Pythonic way to handle a potential division by zero error?

  • A.Check if the denominator is zero using an 'if' statement
  • B.Wrap the division in a 'try-except ZeroDivisionError' block
  • C.Use 'try-except Exception' for all operations
  • D.Import the math module to check the value before division
Show answer

B. Wrap the division in a 'try-except ZeroDivisionError' block
Using try-except follows the EAFP principle. 'If' statements are less robust against concurrent changes. Catching all 'Exception' is too broad, and importing math doesn't prevent runtime errors.

3. What happens if you raise an exception inside a 'try' block that is not caught by any 'except' clause?

  • A.The 'finally' block is skipped and the program crashes
  • B.The error is silently ignored and the program continues
  • C.The exception propagates up the call stack until caught or the program terminates
  • D.The 'else' block is executed instead
Show answer

C. The exception propagates up the call stack until caught or the program terminates
Python bubbles the exception up the stack. 'Finally' always runs even if not caught. The program does not ignore errors, and 'else' only runs if no exception occurred.

4. What is the primary difference between 'ValueError' and 'TypeError'?

  • A.ValueError is for logic errors, TypeError is for syntax errors
  • B.ValueError is for inappropriate argument values, TypeError is for inappropriate object types
  • C.TypeError is for math errors, ValueError is for object errors
  • D.There is no difference, they are interchangeable
Show answer

B. ValueError is for inappropriate argument values, TypeError is for inappropriate object types
ValueError is for when the object type is correct but the value is unacceptable (e.g., int('a')). TypeError is for when an operation is performed on an incompatible type.

5. In a 'try-except-else-finally' construct, when does the 'else' block execute?

  • A.Whenever the 'try' block finishes execution
  • B.Only when an exception is raised in the 'try' block
  • C.Only when no exception is raised in the 'try' block
  • D.Before the 'except' block, if an error occurs
Show answer

C. Only when no exception is raised in the 'try' block
The 'else' block runs only if the code in the 'try' block completes without raising an exception. It is not for exceptions; 'finally' is for cleanup.

Take the full Python quiz →

← Previoustry / except / else / finallyNext →Raising Custom Exceptions

Python

78 lessons, free to read.

All lessons →

Track your progress

Sign in to mark lessons done, score quizzes and keep notes.

Open in the app