Concepts / Error Handling with Try-Except

Error Handling with Try-Except

Defensive programming means asking 'What could go wrong?' before your program reaches users.

  • Programming

The Empty-Line Failure

Imagine a program that reads lines from a user. It skips lines beginning with a hash symbol, stops when the user types done, and prints every other line. The program works for ordinary input, but it crashes when the user presses Enter without typing anything. That empty line exposes a gap in the program's defensive thinking.

Defensive programming means asking what could go wrong before users encounter the program. Empty input is an edge case: it falls outside the expected happy path and must be handled deliberately.

python
Output
IndexError: string index out of range

Tracing the Missing Character

The expression line[0] asks Python to retrieve the character at index 0. This is valid only when line contains at least one character. An empty string contains zero characters, so there is no character at index 0. Python therefore raises IndexError with the message string index out of range.

index 0request index 0hellostring0hIndexErrorindex 0 does not exist1eempty stringno characters2l
What happens when a program requests index 0 from a nonempty string and from an empty string?

The important point is the order of operations. The program attempts the indexing operation before it has checked whether the string contains a character. The solution is to make the safety check happen first, or to use an operation that performs the prefix check safely.

What do you think happens?

What happens when line is an empty string and the program evaluates line.startswith('#')?

  • It raises IndexError
  • It returns False
  • It returns True
Reveal answer

Answer: It returns False

startswith() safely checks whether a string begins with a given substring. For an empty string, it returns False without trying to retrieve a missing character.

Prefix Checks with startswith

The clearest fix for checking whether a line begins with a hash is to replace direct indexing with startswith(). The method expresses the intended question directly: does this string start with a hash? It returns False for an empty string instead of raising IndexError.

python
Input linestartswith('#')Meaning
empty stringFalseThere is no first character, but no error occurs
helloFalseThe line does not begin with a hash
#commentTrueThe line begins with a hash

Safe prefix results for the three cases described in the source material

empty lineempty lineline[0]retrieves index 0IndexErrorempty inputstartswith('#')checks a prefixFalseempty input
How does startswith() differ from manually retrieving the first character when the input may be empty?

Prefer startswith() for this specific task. It is shorter, clearer, and designed to test a string prefix safely. Use direct indexing only when you genuinely need to access the character itself and have protected that access.

The Guardian Pattern

The guardian pattern places a safety condition before an operation that could fail. For this problem, the guard checks that the line has at least one character before the program evaluates line[0].

python
evaluate firstFalseTrueTruelineinput stringlen(line) > 0first conditionFalsedo not evaluate line[0]skip linewhen both conditions aretrueline[0] == '#'second condition
How does left-to-right evaluation allow the length check to protect the later index lookup?

Python evaluates an and expression from left to right and stops as soon as its result is determined. If len(line) > 0 is False, the complete and expression cannot be True. Python therefore does not evaluate line[0] == '#'. Because the unsafe lookup is skipped, the empty string cannot cause this IndexError.

Following the Guardian

Determine what the guardian expression does when line is empty and when line is '#comment'.

Empty line: The length check len(line) > 0 is False. Python stops evaluating the and expression, so line[0] is never accessed.

Comment line: The length check is True, so Python safely evaluates line[0] == '#'. The first character is a hash, so the complete condition is True.

The guardian prevents the index lookup for an empty line and permits it for a nonempty line.

Choosing the Safer Technique

TechniqueHow it prevents the failureBest fit
startswith()Checks the prefix without directly retrieving a missing characterChecking whether a string begins with a particular substring
Guardian patternChecks the string length before evaluating the index lookupCases where later logic needs indexed access

For the hash-prefix problem, startswith() is usually the better choice because it says exactly what the program wants to know. The guardian pattern remains important because it demonstrates a general defensive technique: validate a prerequisite before performing a dependent operation.

Mistakes to Avoid

  • Accessing line[0] before checking whether the line contains a character

    An empty string has no index 0, so Python raises IndexError.

    Fix: Use line.startswith('#') or place len(line) > 0 before the index test in an and expression.

  • Assuming normal test input represents every possible user action

    A user can press Enter without typing anything, producing an empty line.

    Fix: Ask what unexpected or edge-case input could reach each operation before the program is shipped.

  • Putting the unsafe index check first in the guardian expression

    Python evaluates from left to right, so line[0] is attempted before the length guard.

    Fix: Put len(line) > 0 first so a false guard prevents the later lookup.

  • Using the guardian pattern when a direct prefix method communicates the intent more clearly

    Although safe, this is more indirect for a simple prefix question.

    Fix: Prefer startswith('#') when the goal is only to test whether the line begins with a hash.

Practice Check

MEDIUM

A program checks whether an input string begins with the letter A. Explain which technique you would choose if you only need to test the prefix, and explain how the guardian pattern would protect a direct index lookup.

Hints
  • For a prefix-only question, choose the method designed to check a prefix safely.
  • For direct indexing, check that the string has at least one character first.
  • Remember that and evaluates from left to right and can skip its right side.

What do you think happens?

Will this condition attempt to evaluate line[0] when line is empty: len(line) > 0 and line[0] == '#'

Reveal answer

Answer: No. The length comparison is False, so Python short-circuits the and expression and does not evaluate line[0] == '#'.

The guard comes first. Since the result is already known to be False, evaluating the second condition is unnecessary and would risk the IndexError.

Defensive Programming Takeaways

  1. Defensive programming starts by asking what could go wrong before users run the program. Directly accessing index 0 fails for an empty string because no character exists there. Use startswith() when you need a safe prefix check. Use the guardian pattern when a length check must protect a later index operation. Place the guard first because Python's left-to-right short-circuit evaluation can prevent the unsafe expression from running.

Key Takeaways

  • Empty strings do not have a character at index 0, so direct access can raise IndexError.
  • startswith() safely checks a string prefix and returns False for an empty string.
  • The guardian pattern checks a prerequisite such as len(line) > 0 before accessing an index.
  • Short-circuit evaluation works only when the safe guard appears before the potentially failing condition.
  • Anticipating edge cases makes programs more robust, user-friendly, and easier to debug.