Errors and failure.
So far, we have talked about programs that work correctly. Instructions execute. State transforms. Output appears.
But programs do not always work correctly. Sometimes they produce wrong results. Sometimes they stop unexpectedly. Sometimes they fail.
Today, we will understand failure. Not how to fix it — that comes later. But how to think about it. What failure means. Why it happens. And why thinking about failure is part of programming.
Let us be honest: programs fail all the time.
Not just bad programs. All programs can fail. Failure is part of the reality of software.
What does failure look like?
Sometimes a program produces the wrong answer. You expected five, you got six. The logic was incorrect.
Sometimes a program stops unexpectedly. You were using it, and suddenly it crashes. The process terminates.
Sometimes a program does nothing at all. It hangs. It waits forever. It never completes.
Sometimes a program does something harmful. It deletes data. It corrupts files. It causes damage.
All of these are failure. The program did not do what it was supposed to do. Something went wrong.
Not all failures are the same. Let us distinguish two kinds.
The first kind: bugs. A bug is a mistake in the program. The programmer wrote something wrong. The logic is flawed. The code does not correctly express the intended behavior.
Bugs are errors in the program itself. They exist in the code. They cause wrong behavior every time the flawed code executes.
The second kind: expected failures. These are failures that can happen even when the code is correct. They come from the environment or the input.
For example, a program tries to read a file, but the file does not exist. The code is correct — it tries to read a file as intended. But the file is missing. The program cannot do what it was asked to do.
Another example: a program receives invalid input from a user. The user entered text where a number was expected. The code is correct — it expects a number. But the input is wrong.
These are expected failures. They are not bugs. They are situations that correct code must anticipate and handle.
Let us look more closely at bugs.
A bug is a mismatch between what the programmer intended and what the code actually does.
Consider this: you want to compute the average of two numbers. You write "sum divided by three" instead of "sum divided by two." That is a bug. The code is wrong. It will always produce the wrong answer.
Or: you want to repeat something five times. You write "repeat while counter is less than four." That is a bug. The repetition stops too early. It only runs four times.
Bugs come from mistakes. Typos. Misunderstandings. Incorrect logic. Forgetting an edge case.
Bugs are the programmer's responsibility. The programmer wrote the wrong code. The fix is to correct the code.
Bugs can be subtle. The program might work correctly in most cases but fail in rare situations. These are the hardest bugs to find.
Now let us look at expected failures.
These are failures that happen even when the code is correct. The problem comes from outside the program.
Files that do not exist. Network connections that drop. Disks that run out of space. Users who provide invalid input. Hardware that malfunctions.
The program cannot prevent these situations. They are not caused by bugs. They are caused by the environment.
But the program must deal with them. A robust program anticipates that things might go wrong and handles it gracefully.
What does handling mean? It depends on the situation.
Sometimes it means telling the user what went wrong. "File not found."
Sometimes it means trying again. "Connection lost. Retrying..."
Sometimes it means choosing an alternative. "Primary server unavailable. Using backup."
Sometimes it means stopping safely. "Cannot continue. Saving work and exiting."
Expected failures are part of the program's behavior. The program should define what happens when things go wrong.
Here is an important idea: failure is part of the program's behavior.
We usually think of a program's behavior as what it does when everything works. But behavior also includes what it does when things fail.
A complete specification of a program includes: what happens when input is valid? And what happens when input is invalid? What happens when resources are available? And what happens when they are not?
If you only think about the happy path — everything working perfectly — you have only thought about half the problem.
Good programs define their behavior in all situations. Success and failure. Normal and exceptional.
This does not mean the program must always succeed. It means the program must always behave predictably. Even when it fails, it should fail in a defined, understandable way.
How do you know if a program has failed?
Sometimes it is obvious. The program crashes. An error message appears. Output is clearly wrong.
Sometimes it is subtle. The output looks reasonable but is slightly incorrect. The program runs but produces wrong results. You might not notice for a long time.
Detecting failure requires knowing what success looks like.
If you know what the correct output should be, you can compare and check. If the output does not match expectations, something went wrong.
This is why understanding the expected behavior is so important. You cannot find failures if you do not know what correct behavior is.
Testing is how we systematically check for failures. We run the program with known inputs and verify the outputs match expectations. If they do not, we have found a failure.
How does failure relate to our model of state?
Remember: a program transforms initial state into final state.
When a program fails, something goes wrong with this transformation.
Bug failure: the transformation itself is wrong. The instructions do not correctly transform state. The final state is not what it should be.
Expected failure: the transformation cannot complete. Some required resource is missing. Some precondition is not met. The program cannot reach the intended final state.
In both cases, the state is affected.
With a bug, the state might be completely wrong. Values that should be one thing are another.
With an expected failure, the state might be incomplete. The transformation stopped partway through. Some work was done, some was not.
Understanding failure means understanding how it affects state. What state is the program in when it fails? Is the state consistent? Is it recoverable?
Good programmers think about failure.
When writing code, they ask: what could go wrong here?
Could the input be invalid? Could the file be missing? Could the network be unavailable? Could the value be too large?
For each possibility, they decide: what should happen?
Sometimes the answer is: this cannot happen, it is fine to ignore it. But this should be a conscious decision, not an oversight.
Sometimes the answer is: this could happen, and here is how to handle it.
Thinking about failure is not pessimism. It is realism. In the real world, things go wrong. Programs that ignore this reality are fragile. Programs that account for it are robust.
The goal is not to prevent all failure. That is impossible. The goal is to handle failure gracefully when it occurs.
There is one more thing about failure: it teaches us.
When a program fails, we learn something. We learn what we did not understand. We learn what we did not anticipate.
Bugs reveal gaps in our logic. Expected failures reveal assumptions we made about the environment.
Debugging — finding and fixing failures — is a core skill. It is how we improve as programmers.
Do not be discouraged by failure. Every programmer writes programs that fail. The difference is what you do next.
You investigate. You understand what went wrong. You fix it. You learn from it.
Failure is not the opposite of success. Failure is a step on the path to success.
Let us summarize.
Programs can fail. They can produce wrong results, stop unexpectedly, or behave incorrectly.
There are two kinds of failure. Bugs are mistakes in the code. Expected failures come from the environment.
Bugs are the programmer's responsibility. The code is wrong and must be fixed.
Expected failures must be anticipated. The program should define what happens when things go wrong.
Failure is part of the program's behavior. Good programs behave predictably in both success and failure.
Detecting failure requires knowing what success looks like. Testing checks that outputs match expectations.
Thinking about failure makes programs robust. Ask: what could go wrong? What should happen?
Failure teaches us. Every failure is a chance to understand and improve.
Errors and failure. This is the reality of programming.