The Quiet Terminal

← Beginner

try/except: failing without dying

2026-07-22 · 3 min read

For most of my first year, my programs had two states: working and dead. A traceback is friendlier than it looks once you read it from the bottom up, but a program that dies because someone typed cold where a number belonged isn't a program you can hand to another human. try/except is how a crash turns into a message.

Wrap only the risky line

Here's the crash in miniature:

raw = "cold"
minutes = int(raw)
ValueError: invalid literal for int() with base 10: 'cold'

The temptation is to wrap the entire program in a try so nothing can ever crash. Resist it. A try block is a claim that the lines inside might legitimately fail — so wrap only the line that can, and name the exception you expect:

try:
    minutes = int(raw)
except ValueError:
    print(f"{raw!r} isn't a number - using 30")
    minutes = 30

A bare except: — no name — catches everything, including the TypeError from a typo three lines up and the KeyboardInterrupt from Ctrl+C, which I once learned the hard way when a script simply refused to die. Naming the exception keeps the failure honest: you handle what you expected, and genuine bugs still stop the program and get read.

else and finally, in one breath

Two optional blocks complete the shape:

try:
    with open("last_run.txt", encoding="utf-8") as f:
        stamp = f.read().strip()
except FileNotFoundError:
    stamp = "never"
else:
    print("last run:", stamp)    # only if the try succeeded
finally:
    print("check complete")      # either way

First run, there's no file, so you get just check complete; every run after, the stamp prints first. else runs when nothing failed — keeping the success path out of the try keeps the try small. finally runs no matter what happened, which makes it the place for "always do this". In practice: try/except almost always, else now and then, finally about once a year.

Turning a crash into a conversation

Put together, here's the pattern I use for any input that might be wrong. The loop doesn't die on bad input; it argues back:

while True:
    raw = input("minutes to back up (a number): ")
    try:
        minutes = int(raw)
        break
    except ValueError:
        print(f"{raw!r} isn't a number - try again")

print(f"starting the {minutes}-minute backup")

Type ten and it asks again; type 30 and it moves on. The except names ValueError because the last line of the crash told me so — when you're unsure which exception to catch, trigger the failure once on purpose and read the name. That's the connection between the two skills: tracebacks tell you what failed, and try/except is what you do about the failures you expected.

One boundary I try to respect: try/except is for expected failures — bad input, missing files, a flaky network — not for hiding bugs. If an exception surprises you, let it crash and read the traceback. Errors are information, and a program that never crashes isn't finished; it's deaf.