Introduction
It’s been a while since I last wrote a post about programming that delves into the general approach to programming, rather than focusing on concrete subjects like programming principles, architecture, or specific code examples. So, this time, I’ve decided to explore the topic of programming failures.
This post isn’t about failure in a broad sense, although I will touch upon that to distinguish it from programming failures. Nor is it about long-term failures in programming, such as missing deadlines or releasing an unoptimized or buggy game. Instead, I want to discuss the small, everyday failures: spending time implementing a feature that doesn’t work, choosing the wrong pattern as a solution, making an incorrect architectural decision, or even using the wrong method for a particular task.
I believe these small failures are the most effective way for a programmer to evolve. When you stop failing, your rate of improvement drops drastically, and trying to avoid these failures is one of the worst things a programmer can do. But before I delve into my thoughts on “small” programming failures, let me briefly discuss failure in general.
Failure In General
You’ve likely read or heard that failure is as important as success, sometimes even more so because when you fail, you tend to reflect on the reasons for that failure longer and more intensely than you do on the reasons for your success. While there are some fields where failure is indeed considered as important as success, for example, in innovation and entrepreneurship, I don’t believe this holds true universally. Regarding the impact failure can have on an individual, I categorize failures into three types:
Beneficial Failure Sometimes, failure is beneficial for the reasons mentioned earlier: it compels you to think more deeply about why you failed, what your shortcomings are, and how to avoid them in the future. In short, failure can occasionally be one of the most valuable learning experiences.
Inconsequential Failure Other times, failure is inconsequential. It’s neither good nor bad; you fail, and you move on. You don’t gain significant experience from it, or the experience you do gain is roughly equivalent to what you would have learned had you succeeded.
Detrimental Failure Finally, there are instances where failure is truly detrimental. This isn’t necessarily due to the consequences of the failure itself —one could argue that those consequences add to the learning experience by forcing deeper reflection— but because failure can be used as an excuse. Let me illustrate with an example: You decide to create a simple game that you believe you can finish in a week. After a month, you haven’t even completed a third of it. This isn’t because you underestimated the time commitment or overestimated your abilities, but because instead of coding the game, you spent your time lounging on the couch binge-watching your favorite TV show or procrastinating by watching YouTube videos all day (or reading blog posts like this one).
Instead of feeling remorseful, you rationalize your failure: it’s okay because you have to fail many times before you succeed, or it’s okay because you gain experience from failure, or any other excuse you can conjure. But the reality is this: you’ve gained nothing from this failure. What experience did you acquire? That you shouldn’t spend all day on the couch? You should already know that, and if you don’t, it certainly shouldn’t take a month to realize it; you could have understood it in a couple of days. Instead, you don’t feel guilty; you use failure as an excuse and move on. You’ve learned nothing.
Failures In Programming
Programming is a different animal altogether. It’s fundamentally different from everything humankind has done before for one key reason: the design phase is the expensive part, while the production phase is relatively cheap. Make no mistake, when you write code, you’re not producing a program; you’re designing one. Once that design is complete, a compiler takes responsibility for producing the program based on your design.
This distinction that programming holds, compared to all other human endeavors until now, is responsible for many aspects of programming, from various architectural principles to specific systems for managing programming teams. But in this post, it also explains how small failures —which can occur frequently and even multiple times each day— can affect a programmer.
Enjoy the Process as Much as the Result
This advice is particularly relevant for beginner programmers, because those who don’t embrace this mindset will likely never progress beyond the beginner level.
Having a desire to create something, setting a goal, and working towards a result are all positive and necessary aspects. However, enjoying the process is equally important. Writing code isn’t like some other jobs where, after a few years, you can operate on “auto-pilot.” The image portrayed in movies of the high-level programmer frantically hitting the keyboard without pause simply doesn’t exist.
There will be moments when you enter a state of “flow,” where every answer comes to mind faster than you can type. But more often than not, programming will be as it is now: you’ll be staring baffled at the screen, trying to understand what’s happening, how to solve a problem, where a nonsensical bug is hiding, or even being certain that your code is correct but the reason it doesn’t work lies in someone else’s code.
Don’t misunderstand me; these challenges won’t persist for the same reasons they do now. Code you write today will eventually seem trivial, but you won’t want to write that kind of code anymore. Instead, you’ll always aspire to write something bigger, better, or more complex, and being comfortable with failing at that, not just once but repeatedly, is a crucial part of not just becoming a programmer, but truly being one.
Unless someone enjoys that process: failing, failing, and failing again before getting something right; struggling to find a bug, fixing it, and then realizing the fix was flawed; failing to implement a feature, then implementing it, and after some time realizing there are unaccounted-for edge cases requiring a restart or rewrite, even making a decision like, “Let’s use inheritance for this implementation,” and realizing after a week or two that it was a bad decision necessitating a complete refactoring, unless someone enjoys this iterative process of small failures, then programming might not be the right field to invest in.
Enjoying the process of writing code —which is inherently a process of multiple small failures— as much as the end result is a kind of “masochism” necessary for a programmer, and this leads to the second reason why small failures are important for a programmer.
Delayed Gratification
Creating something —anything— complete in code takes time, a significant amount of time. Even if someone could write only correct code all the time, it would still be a lengthy process. This time is an emotional rollercoaster for any programmer. One day, you might feel like the solution you’ve crafted is so brilliant that perhaps only a handful of other people on the planet could have achieved such elegance. The next day, you might feel utterly useless, incapable of even implementing the simplest things, perhaps even feeling like you can’t even type correctly without makng a misake evry few words.
These small failures in programming serve as training for managing emotional highs and lows. In successes, you don’t become overly elated because you remember your previous failures, and in failures, you don’t become too despondent because you know that eventually you will overcome them. Small failures act as a safety net, keeping you near a central emotional line throughout a project, and this is what trains someone to appreciate the delayed gratification that programming can offer.
It’s not about building something right now, it’s not about always finding the best way to do something, or even writing code that will exist in the final project. It’s about writing throwaway code, refactoring numerous times, and persevering through bugs and difficulties because the eventual result will be worthwhile.
Without small failures, if writing only correct code were possible, the final result would never feel so rewarding. It would simply be the next step in a series of steps that could span your entire career. Without those small failures, a programmer might become numb from always writing correct code, to the point where they would never be able to feel satisfaction upon completing a project after all the time invested.
Continuous Improvement
Small failures in programming are what drive programmers to improve each day. In a field where you can never know even 10% of everything that exists, if it were possible to master something to the point of always writing correct code, it would be disastrous. Programming evolves rapidly; new tools, principles, programming languages, everything, in fact, changes constantly. By failing to do something, you invariably feel the need to learn something different, to try a new approach, in the hope of reducing your failures. Without failures, there would be no impetus for change.
In any other field that isn’t as volatile as programming, having no failures and, by extension, not changing could be sustainable. In programming, not feeling the need to learn new things, not feeling the need to improve, even if what you currently know is 100% correct all the time, would render you obsolete within five to ten years. What’s the point in being perfect in a technology that’s no longer used, or in a programming language that very few people use anymore?
Everyday failures in programming are the reason to keep improving. We strive to get better because our failures remind us of our imperfections, not because we succeed in everything we do without ever stumbling. Embrace every mistake as an opportunity to read something new, to try a different approach instead of feeling discouraged, because these mistakes are what shape experienced programmers, what makes those programmers who can identify a solution or spot a problem in a codebase with just a glance. It’s not that they’ve written the exact same code before; it’s that they’ve encountered similar failures many times before.
Trying to “Cheat” By Getting the Solution
And all of the above brings us to this point, to discuss what I believe is one of the worst things a programmer can do: trying to create a program without writing code, trying to build an app by writing as little code as possible, looking for ways to obtain code that implements the desired functionality from elsewhere: a forum, a Discord server, Stack Overflow, or from an AI LLM.
This isn’t about the correctness of the code produced by these methods. It’s about trying to succeed without experiencing the multitude of small failures along the way. Let’s assume the code obtained is always correct. In that case, getting pre-written code from another source is akin to a student getting math answers from a textbook with solutions. Even if someone understands the solution, it’s not the same as failing multiple times before independently arriving at the solution. There might be situations where getting a ready-made solution seems advantageous, but the disadvantage of missing out on those failures outweighs any perceived benefit.
Consider productivity as an example. For a math student, solving ten problems in the same time it took to solve two might seem beneficial. However, the problem with this approach is that productivity should never be the primary goal of any programmer. Productivity should be the concern of a CEO, a manager, or whoever is funding the project, but for any programmer, the primary goal should be to become a better programmer, with productivity being a natural byproduct of that growth. Productivity should always take a backseat to the goal of continuous improvement.
Of course, learning new ways to solve problems does contribute to a programmer’s improvement. But that’s precisely the issue with getting a solution from an external source instead of discovering it independently: the excuse of improvement still exists because some knowledge is gained, but the amount of improvement derived from being handed the solution is nowhere near the amount gained from the iterative process of failing multiple times before succeeding.
A failure in a programming solution is not the same as a failure in other aspects of life, as I mentioned at the outset. We might be conditioned to think —primarily due to the structure of the education system— that failures are undesirable. However, in programming, at least, every failure yields something valuable for the future. You gain experience that informs your initial assessment of a new project, allowing you to anticipate what will work, but more importantly, what won’t work.
There’s also the argument that only novice programmers should avoid readily available solutions, while it’s acceptable for experienced programmers. This would be true if experienced programmers didn’t also need to learn new things, didn’t also need to improve, didn’t need to become better than their current level. But as I explained earlier in the “Continuous Improvement” section, no one reaches a point where further improvement is unnecessary, and the limited improvement gained by being handed a solution will eventually lead to other programmers at your level surpassing you.
Even if we assume that the handed-down solution isn’t correct and requires fixing, it’s still insufficient. This isn’t our failure to rectify; it’s the failure of whoever provided the solution, and that’s a completely different scenario. All the assumptions, chains of thought, incorrect knowledge, and logical leaps that would have led us to an incorrect implementation never occurred on our part. These are the “small failures” described in this post that, even when we have to fix an incorrect solution given to us, we haven’t personally experienced.
The Illusion of Improvement
The problem with solutions handed down to a programmer, in contrast to the programmer failing many times until finding the solution independently, is that it creates an illusion of improvement. If we have two programmers of the same level, regardless of what that level is (let’s say both are senior level, whatever that means), and the first one starts coding using tools that provide solutions, they will still improve, but their rate of improvement will be significantly hampered. Being handed a solution is like taking ten steps forward and then, after some time, nine steps backward. This creates the illusion of maintaining your rate of improvement; you gain ten levels, which seems like substantial progress, and even after applying the solution, because some knowledge is acquired, you might lose nine of those levels but still be slightly better than before. In contrast, the second programmer, who has to work through their failures before achieving a successful solution, improves at a steady rate of, say, three levels each time.
In the short term, the first programmer may appear more skilled and productive than the second. However, because their growth is limited by relying on handed-down solutions, the second programmer will eventually surpass them. Why? Because they’ve built context. They understand not just what works, but why something else didn’t. That knowledge is invaluable when projects become complex and nuanced.
These failures would have contributed to a deeper understanding of the context to which the solution must be applied, the specific needs of the project, any future requirements and potential problems, as well as a better understanding of how each solution integrates with the project as a whole and its advantages and disadvantages, not just in isolation but in relation to everything else happening in the project. After all, the disadvantages of each solution will be firsthand knowledge to them: they’ve used it and it was the wrong choice in a previous situation, so that failure is the experience that will be beneficial now. That context-driven insight is what makes their eventual solution not only effective but more sustainable in the long run.
Conclusion
Failing in programming is not something any programmer should fear; instead, failure should be actively sought. Placing oneself in uncomfortable situations where one anticipates failure before achieving success is something every programmer should desire. If the concern is, “But how do I know I will eventually succeed?”, the answer is that you gain confidence in this through repeated past failures.
As programmers grow, they don’t just collect technical skills. They build resilience,they build the understanding that in an unfamiliar situation, lacking knowledge about the environment, the tools, and the programming language used, they will fail many times, but that’s acceptable because they are accustomed to it. It doesn’t matter because they have already failed many times before, every day, and eventually, they found the correct solution.
Because ultimately, when you can’t rely on someone else for the solution, the only thing that will hinder you from solving the problem is a lack of prior failures.
Thank you for reading and as always, if you have any questions or comments you can use the comments section, or contact me directly via the contact form or by email. Also, if you don’t want to miss any of the new blog posts, you can subscribe to my newsletter or the RSS feed.