📌 Snapshot
- Learn how Python survives runtime errors with try, except, else and finally, and which block runs when.
- Syntax errors, exceptions and logical errors compared, plus all twelve built in exceptions with their triggers.
- How raise and assert create exceptions, and how Python hunts for a handler through the chain of function calls.
- Output tracing tables for every combination of error and no error, the part of this chapter CUET tests most.
📖 Detailed Notes
Written and reviewed by Rohit Singh, Computer Science & Commerce faculty at UniDrill (MCA, Osmania University · 6 years teaching). Based on NCERT Computer Science Class XII, Chapter 1.
What happens when the user types "ten" instead of 10?
A shop billing program asks, "How many items?" and the customer types ten. The call int("ten") cannot convert the text, Python stops the program mid-bill and prints a red wall of text. Everything the cashier had entered so far is gone.
Exception handling is the cure. It lets your program notice the problem, say "Please type digits", ask again and carry on, instead of crashing. This chapter is about three questions: what kinds of errors exist, how Python reports them, and how you catch them. The last one, which block of a try statement runs when, is the single most tested skill in the chapter.
Three kinds of errors
Not every mistake in a program is an exception. Python meets errors at different moments, and the moment decides what you can do about it.
| Kind | Found when | What Python does | Example |
|---|---|---|---|
| Syntax error (parsing error) | Before the program runs, while reading the code | Refuses to run any of it, names the error and gives a short description | print "Hello" or an if line with no colon |
| Exception (runtime error) | While the program runs, on a line that is correctly written | Stops at that line, prints the error name and reason, unless your code handles it | print(50 / 0) |
| Logical error | Never reported | Runs happily and gives the wrong answer | Averaging with a + b / 2 instead of (a + b) / 2 |
Why the difference matters. A syntax error breaks the rules of the language, so Python cannot even understand the program and will not start it. An exception is different: the statement is fine as written, but something about the data at that moment makes it impossible, such as dividing by zero or opening a file that does not exist. Only exceptions can be handled by your own code. A logical error produces no message at all, which makes it the hardest to find.
An exception is a Python object that represents an error. Once it is raised, normal execution is interrupted. SyntaxError is itself an exception object too, but it is the only one that appears for a program with wrong syntax. Every other exception can occur only when the syntax is correct.
The twelve built in exceptions
Python's standard library already defines an exception for every common mistake. When one occurs, the interpreter shows its name and the reason. You can also define your own (user defined) exceptions, but the exam concentrates on these twelve.
| Exception | Raised when | Typical trigger |
|---|---|---|
| SyntaxError | The rules of the language are broken | print "Hi" |
| ValueError | A function gets the right type of argument but an unsuitable value | int("abc") |
| IOError | An input or output operation fails, such as a missing file | open("nofile.txt") |
| KeyboardInterrupt | The user interrupts the running program, for example by hitting the Delete or Esc key | Interrupting a long loop |
| ImportError | The module to be imported is not found | import nosuchmodule |
| EOFError | input() reaches the end of the input without reading any data |
input() when no more input is available |
| ZeroDivisionError | The denominator of a division is zero | print(50 / 0) |
| IndexError | An index or subscript is out of range | [10, 20, 30][5] |
| NameError | A local or global name is not defined | print(var + 40) with no var |
| IndentationError | The indentation of the code is wrong | The body of an if not indented |
| TypeError | An operator or function gets a value of the wrong type | 10 + '5' |
| OverflowError | A calculation result is too large for the numeric type | math.exp(1000) |
Pause and predict: what happens with int("3.5"), and what happens with int(3.5)?
Check your answer: int("3.5") raises ValueError, because the argument is a string (a fine type for int) but the text is not a whole number. int(3.5) raises nothing and gives 3, because a float is converted by dropping the fraction. ValueError means "right type, wrong value", while TypeError means "wrong type altogether".
Beyond the textbook. For the exam: IOError is raised when a file operation fails, for example opening a file that does not exist. In reality: in Python 3, IOError is just another name for OSError, and a missing file actually raises FileNotFoundError, which is a more specific kind of OSError. Code written as except IOError: still catches it, so the exam answer and the real behaviour agree.
raise: forcing an exception
Python raises exceptions on its own when it detects an error. You can also raise one yourself when your program finds a situation that should not continue. The syntax is raise exception-name[(optional argument)], where the optional argument is a message string.
Raising an exception interrupts the normal flow, and no further statement in the current block executes. Study this program:
| Code |
|---|
numbers = [40, 50, 60, 70] |
length = 10 |
if length > len(numbers): |
raise IndexError |
print("NO EXECUTION") |
else: |
print(length) |
Since 10 is greater than 4, the if branch runs and raise IndexError fires. The print("NO EXECUTION") right after it is never reached. The program ends with a traceback, which lists the calls that led to the error, and its last line names the exception.
| Output |
|---|
Traceback (most recent call last): |
IndexError |
assert: a guard at the start of a function
The assert statement tests an expression. If the expression is True nothing happens. If it is False, Python raises AssertionError with your message. The syntax is assert Expression[, arguments]. It is used at the beginning of a function, or right after a call, to check that the input is valid.
| Code |
|---|
import math |
def root(n): |
assert n >= 0, "OOPS... Negative Number" |
return math.sqrt(n) |
print(root(16)) |
print(root(-4)) |
| Output |
|---|
4.0 |
AssertionError: OOPS... Negative Number |
The first call passes the test. The second fails it, so return is never reached and the program stops (the traceback lines in between are left out above).
| Feature | raise | assert |
|---|---|---|
| Raises when | Always, when the statement runs | Only if the tested expression is False |
| Exception it raises | Any exception you name | AssertionError only |
| Typical use | Signal a problem you detected | Check that inputs are valid |
| Optional extra | A message in brackets | A message after a comma |
Beyond the textbook. For the exam: assert raises AssertionError when the expression is False. In reality: Python can be started in an optimised mode that removes every assert statement before the program runs. So real programs never rely on assert to check user input. They use an if with raise instead, and keep assert for debugging checks.
Why handle exceptions at all?
Without a handler, the first exception ends the whole program. Handling gives you four benefits. Python sorts errors into distinct types, so each can get its own response. The error response code is separated from the main logic, so the main logic stays readable. The interpreter reports the exact position of the error. Both built in and user defined exceptions can be handled.
How Python looks for a handler
When an error occurs, Python builds an exception object holding the type of error, the file name and the position. Handing this object to the runtime system is called throwing the exception. The runtime then looks for a piece of code that can deal with it, called an exception handler, first in the current function. If there is none, it searches the functions that called this one, in reverse order. This list of waiting calls is the call stack, and passing the problem outward is forwarding the exception. Running a suitable handler is catching. If every function in the stack has been searched without luck, the program terminates and prints the traceback.
| Step | Term | What happens |
|---|---|---|
| 1 | Error occurs | An exception object is created |
| 2 | Throwing | The object is handed to the runtime system |
| 3 | Search | Handler looked for in the current function |
| 4 | Forwarding | If not found, callers are searched in reverse order |
| 5 | Catching | A matching handler runs |
| 6 | Termination | No handler anywhere: program stops with a traceback |
Worked example: tracing the search. Where is the exception of this program caught?
| Code |
|---|
def divide(a, b): |
return a / b |
def calc(): |
return divide(10, 0) |
try: |
calc() |
except ZeroDivisionError: |
print("caught in main") |
| Step | Where Python looks | Handler there? | Result |
|---|---|---|---|
| 1 | divide, where 10 / 0 fails |
No | Forward to the caller |
| 2 | calc, which called divide |
No | Forward to the caller |
| 3 | Main program, inside try |
Yes, except ZeroDivisionError |
Caught, prints caught in main |
Pause and predict: in this program, what changes if the handler is written as except ValueError: instead?
Check your answer: ValueError does not match a ZeroDivisionError, so no handler exists anywhere in the chain. Python prints the traceback and terminates, and caught in main is never printed. A handler catches only the exception type it names.
try and except: catching an exception
Place the suspicious statements in a try block. Every try is followed by one or more except blocks with the handler code. When a statement in try raises an exception, the rest of the try block is skipped and control jumps to the matching except. After that handler finishes, the program continues with the first statement after the whole construct.
| Code |
|---|
try: |
numerator = 50 |
denom = int(input("Enter the denominator: ")) |
quotient = numerator / denom |
print("Division performed successfully") |
except ZeroDivisionError: |
print("Denominator as ZERO is not allowed") |
except ValueError: |
print("Only INTEGERS should be entered") |
print("OUTSIDE try..except block") |
| User types | Where it breaks | Output |
|---|---|---|
5 |
Nowhere | Division performed successfully, then OUTSIDE try..except block |
0 |
numerator / denom raises ZeroDivisionError, so the success message is skipped |
Denominator as ZERO is not allowed, then OUTSIDE try..except block |
abc |
int(...) raises ValueError before the division |
Only INTEGERS should be entered, then OUTSIDE try..except block |
Four rules follow from this table. Only the first matching except runs, and the rest are skipped. A statement placed before the try is not protected, because the try must surround the risky code. A bare except: with no exception name is a catch-all, and it must be the last handler. Python reads the handlers from top to bottom, so a catch-all placed first would swallow every exception and hide the specific ones below it.
Beyond the textbook. For the exam: a bare except must be written last. In reality: Python enforces this itself and reports a SyntaxError if a bare except: is not last. A bare except also catches KeyboardInterrupt, so a user cannot stop the program with the interrupt key (Ctrl+C on most systems). Careful programmers name the exceptions they expect.
else and finally: the full construct
Two optional clauses complete the statement. The else block comes after all the except blocks and runs only if the try block raised no exception. The finally block comes last and runs always: after success, after a handled exception, and even when the exception is not handled. In the unhandled case the exception is raised again after the finally block finishes. The order is fixed: try, then except blocks, then else, then finally.
| Code |
|---|
try: |
x = int(input("X: ")) |
q = 10 / x |
except ZeroDivisionError: |
print("ZE") |
else: |
print("Q =", q) |
finally: |
print("FIN") |
| User types | try block | except | else | finally | Output |
|---|---|---|---|---|---|
5 |
Completes | Skipped | Runs | Runs | Q = 2.0 then FIN |
0 |
Stops at 10 / x |
ZeroDivisionError handler runs | Skipped | Runs | ZE then FIN |
abc |
Stops at int(...) |
No match for ValueError | Skipped | Runs, then the error is raised again | FIN, then a ValueError traceback |
Why use else at all? You could print the quotient at the end of try. The difference is protection: any exception raised inside try is looked after by the handlers, while code in else is outside that protection. Keeping only the risky line in try and the follow up work in else means a bug in the follow up is never mistaken for the error you were expecting.
Why finally exists. Some actions must happen whatever goes wrong. Closing a file is the standard case, since a file left open can lose data. Putting close() in finally guarantees it runs.
Worked example: predict the output.
| Code |
|---|
def test(a, b): |
try: |
r = a / b |
print("A") |
except ZeroDivisionError: |
print("B") |
else: |
print("C") |
finally: |
print("D") |
print("E") |
test(6, 3) |
test(6, 0) |
test("6", 3) |
| Call | What happens | Printed |
|---|---|---|
test(6, 3) |
Division works, so try, else and finally run, then the line after | A, C, D, E |
test(6, 0) |
ZeroDivisionError, so handler and finally run, then the line after | B, D, E |
test("6", 3) |
Dividing text by a number raises TypeError, which no handler matches. finally runs, then the program stops |
D, then a TypeError traceback. E never prints |
The common wrong step is to think finally cancels the error. It does not: finally runs first, but an unhandled exception still ends the program afterwards.
Pause and predict: can finally appear without any except block?
Check your answer: Yes. A try may be followed by finally alone. The code in finally still runs, and any exception is raised again afterwards, since nothing has handled it.
Exam traps
| Trap | Why it tempts | Truth |
|---|---|---|
finally runs only when an error occurs |
Cleanup sounds like error handling | finally always runs |
else runs when an exception occurs |
In English, "else" suggests "otherwise" | else runs only when the try block raised no exception |
finally handles the exception |
It sits beside except |
Only except catches. After finally, an unhandled exception is raised again |
Statements after raise still run |
The lines are right there in the block | Nothing after raise in that block executes |
Several matching except blocks all run |
More handlers feels like more coverage | Only the first matching one runs |
assert raises ValueError |
A false value seems like a bad value | assert raises AssertionError |
A line before try is protected |
It is in the same function | Only the code inside try is protected |
| SyntaxError is not an exception | Syntax errors feel different from runtime errors | SyntaxError is an exception, but it is the only one that arises when the syntax is wrong |
60-second revision
| Idea | Remember |
|---|---|
| Syntax error | Found before running, program will not start |
| Exception | Raised at run time on correct syntax |
| Logical error | No message, wrong answer |
| raise | raise ExceptionName("message") forces an exception; nothing after it runs |
| assert | assert condition, "message" raises AssertionError if the condition is False |
| Handler search | Current function, then callers in reverse (forwarding), else program ends |
| try | Holds the risky statements |
| except | Runs for a matching exception; bare except: goes last |
| else | Runs only if no exception occurred |
| finally | Always runs; used for cleanup such as closing files |
| Order | try, except, else, finally |
Where this chapter connects
Files fail in all the ways this chapter describes, so File Handling in Python uses finally, with and EOFError constantly. The call stack that Python searches for a handler is the same last in, first out idea you meet in Stack.
🎯 Practice MCQs
First 3 questions free · create a free account to unlock the rest — answers & explanations included, no payment needed
Q1. Which of the following statements about syntax errors in Python is correct?
▸ Show answer & explanation
Answer: B
The interpreter finds syntax errors while reading the code, before running it, and will not execute the program until they are fixed. Option A describes an IOError situation, option C describes runtime exceptions, and option D is wrong because shell mode also reports syntax errors immediately.
Q2. Consider: (1) numbers = [40, 50, 60, 70] (2) length = 10 (3) an if block that runs when length > len(numbers) and contains raise IndexError followed by print("NO EXECUTION") (4) an else block that prints length. What is the result?
▸ Show answer & explanation
Answer: D
Since 10 is greater than 4, the if block runs and raise IndexError executes. After a raise, no further statement in that block runs, so the print after it is skipped. Option C is wrong for that reason, and options A and B ignore the raised exception.
Q3. Which statement correctly describes assert in Python?
▸ Show answer & explanation
Answer: D
assert raises AssertionError, not ValueError, when the tested expression is False. Option A describes try with except. Option D is wrong because assert is typically used at the beginning of a function or after a call to check that input is valid.
🔒 12 more practice MCQs
Create a free account to unlock every MCQ in this chapter — answers and explanations included. No payment needed.
Already registered? Just log in and they'll all appear here.
Q4. Which pairing of a built in exception and its cause is INCORRECT?
▸ Show answer & explanation
Answer: C
ImportError is raised when the module being imported cannot be found. The description in option C belongs to EOFError. Options A, B and D are all correctly matched.
Q5. Statement I: The else block of a try statement runs only if no exception is raised in the try block. Statement II: The finally block runs only if an exception is raised in the try block. Which option is correct?
▸ Show answer & explanation
Answer: C
else runs only when try completes without an exception, so Statement I is true. finally runs every time, whether or not an exception occurred, so Statement II is false.
Q6. If the runtime system finds no exception handler in the function where an error occurs, what does it do next?
▸ Show answer & explanation
Answer: C
The runtime moves outward through the call stack looking for a handler. Only when no function in the whole chain has one does the program terminate, so option A is premature. The else block runs only after a try that raised nothing, which makes option D wrong.
Q7. Match List I with List II. List I: (i) NameError (ii) IndentationError (iii) OverflowError (iv) KeyboardInterrupt. List II: (p) A result exceeds the limit of the numeric type (q) A name is not defined (r) The code is not indented correctly (s) The user interrupts the running program. Which option is correct?
▸ Show answer & explanation
Answer: A
NameError means an undefined name, IndentationError means wrong indentation, OverflowError means a result too large for the numeric type, and KeyboardInterrupt means the user interrupted the program. Options B, C and D each swap at least two pairs.
Q8. Consider: (1) a try block that sets numerator to 50, reads denom with int(input()), computes numerator / denom and prints Division performed successfully (2) an except ZeroDivisionError block printing Denominator as ZERO is not allowed (3) an except ValueError block printing Only INTEGERS should be entered (4) a final line, outside the construct, printing OUTSIDE try..except block. If the user enters 0, what is printed?
▸ Show answer & explanation
Answer: B
Dividing 50 by 0 raises ZeroDivisionError, so the success message is skipped and the first handler runs. After the handler, control continues with the statement outside the construct, so that line is printed too, which rules out option D. Option C would need a non numeric input.
Q9. Consider: (1) try: print(10 / 2) (2) except ZeroDivisionError: print("Zero!") (3) else: print("OK"). What is printed?
▸ Show answer & explanation
Answer: A
10 / 2 gives 5.0 in Python 3 and raises no exception. The except block is skipped, and the else block runs because the try block completed normally. Option A forgets the else block, and options C and D assume an exception.
Q10. Consider: (1) try: x = int("abc") (2) except ValueError: print("VAL") (3) finally: print("FIN"). What is printed?
▸ Show answer & explanation
Answer: C
int("abc") raises ValueError, so the handler prints VAL first. The finally block always runs afterwards and prints FIN. Option D has the order reversed, because finally is executed last.
Q11. Consider: (1) try: x = 10 / 0 (2) except ValueError: print("V") (3) finally: print("F"). What happens?
▸ Show answer & explanation
Answer: A
The except block names ValueError, which does not match ZeroDivisionError, so the exception is unhandled. The finally block still runs and prints F, and then the exception is raised again. Option B wrongly assumes finally cancels the error.
Q12. Why does the order of except blocks matter?
▸ Show answer & explanation
Answer: D
Handlers are checked from top to bottom and only the first match runs. A bare except matches everything, which is why it must be written last. The other options describe behaviour Python does not have.
Q13. Consider: (1) try: raise IndexError("Bad") (2) print("Unreached") (3) except IndexError: print("Caught"). What is printed?
▸ Show answer & explanation
Answer: B
The raise statement throws IndexError immediately, so the following print inside the try block is skipped. Control jumps to the matching handler, which prints Caught. Option B wrongly lets a statement run after the raise.
Q14. Assertion (A): The finally clause is a suitable place to close a file. Reason (R): The finally clause runs whether or not the try block raised an exception.
▸ Show answer & explanation
Answer: A
Closing a file must happen even when an error occurs, and finally is guaranteed to run in every case. That guarantee is exactly why finally suits cleanup, so R explains A.
Q15. Which of the following is NOT a built in exception of Python?
▸ Show answer & explanation
Answer: D
The exception for division by zero is named ZeroDivisionError. DivideByZeroError does not exist in Python, and using it would itself cause a NameError. The other three are standard built in exceptions.
📊 Previous-Year Questions
Practise with real CUET Computer Science previous-year papers — every question solved, with the correct answer and a step-by-step explanation.
View solved CUET PYQ papers →Ready to drill Computer Science?
Unlock all MCQs, chapter tests, mocks & PYQs for ₹199/year.
Get UniDrill Pro