By Nick Nathan in
ProgrammingOne of the best ways to dramatically improve your ability to quickly develop stable, functional applications is to learn how to debug more effectively. The faster you can work through problems and fix issues the more you can focus on the creative aspects of development.
After working with dozens of developers over the years and solving hundreds of bugs both during local development and in production I came to realize that debugging is it's own skill independent of what programming language or technology you're using.
One of the indicators of better/more senior engineers was always their ability to rapidly troubleshoot problems whether or not they had the full context. Junior, less senior engineers tended to waste tons of time using ineffective debugging techniques because they didn't actually know what other options were available to them once they had exhausted the few techniques they did know.
There's an entire hierarchy of debugging techniques which more experienced engineers have access to that dramatically increase their ability to debug issues. The better the engineer the further up the hierarchy they can go while solving a problem. More skilled engineers will also have a better intuition of when to move between levels in the hierarchy. The waste a lot less time trapped at one level before moving to the next.
Fortunately, with practice, you can improve in your ability to access new levels of the debugging hierarchy once you know what they are.
You actually already know all the levels but you're probably not experienced operating at every level. All of the levels have a purpose and as you advance you gain the ability to operate across more of the levels simultaneously.
Your natural instinct is to spend as little effort as possible to solve any particular problem. This isn't wrong. The right instinct is to minimize effort while resolving problems so you can get back to doing whatever else you wanted to be doing instead. The problem is that it often takes more time / effort to find the solution as you move up the hierarchy.
You know that feeling when you slowly begin to realize that this bug you thought was small is actually way more complex. You tried a few quick fixes and it's still not working. The syntax all looks fine. You read the logic through a few times and it all makes sense.
You're frustrated because you wanted to be working on A but now you're totally sidetracked trying to figure out X. You know you should probably start doing more research but you think maybe if you just try a different input it will suddenly work. Maybe if you restart your IDE? Maybe you just didn't google it right? All your google search results come up purple ... there's nothing helpful on stack overflow. You keep thinking, "maybe if I just try one more thing it will work" or, "maybe if I just retry it a few times then suddenly it will work". But obviously it doesn't because nothing has changed really. By now you want to put your first through your laptop.
Sound familiar?
More junior engineers can get stuck in this loop of aimlessly googling and retrying the same inputs over and over again hoping that minor tweaks will magically solve the problem. This is very wasteful but very common. I used to spend way too much time stuck in this pattern. By understanding each level in the hierarchy you'll have a better sense of when to stop what you're doing and move up to the next level.
The first level of the debugging hierarchy is almost always where you start when things aren't working. Most of us do this automatically and when you're just starting out as a developer a larger proportion of the bugs you face will result from these types of errors. These errors are the easiest to identify so you should always start here.
Once you've ensured that all your syntax is correct, you don't have any typos, misnamed variables, and the logic flow looks correct when you re-read it you can move onto the next level.
If you're dealing with some kind of runtime error or unexpected results from a production app then you're at level 2. Assuming you don't already know what the error means your first instinct will almost always be to copy the error into google and just see what pops up. If you're lucky it will be a well defined or common error and someone will have posted a solution on a help forum like Stack Overflow or some blog will have written about it.
At this point you're just kind of scanning web pages and looking for quick explanations and potentially small code snippets to copy and paste. If you're not able to find a quick solution on google then you'll advance to the next level.
So google may have been able to give you some guidance but nothing directly applicable so you go back to your editor and start to mess around with different data inputs or configuration settings. At this point you're usually iterating through a bunch of quick and easy alternative approaches. The goal is to try and get more information about the problem to see if you can't fix it without a major code overhaul. This is the trial and error phase.
This is also the most dangerous level and where most developers typically spend way too much time. Sometimes the error can be identified and resolved at this level of troubleshooting but often times engineers spend literally hours making minor tweaks and adjustments, often repeating themselves. You start to get frustrated. You start to get irritated. This is already taking longer than you wanted it to. More junior developers can get trapped here, throwing hail marry after hail marry hoping something will work. They restart the app dozens of time. Restart their IDE. Break out the prayer beads cus there's ghosts in the machine.
The best developers will spend as little time at the trial and error level as possible. The goal here is to try and eliminate problem areas and narrow the scope of the problem. Can you identify roughly where the error is being caused? Can you consistently reproduce it by following a series of steps? If educated guessing doesn't yield any results fairly quickly it's time to move on.
In this phase you go back and revisit all those stack overflow solutions which were really long and complicated that you basically ignored on the first pass. They seemed scary and overwhelming but now you have no choice but to actually read through them in detail. The goal here is to confirm whether or not this is the same problem that you have or if it's related enough to help you solve the problem.
It's also common at this point to go back to your original logs and errors and make sure you read the errors in more detail. Do you really understand the exception message? What does it actually say? Probably at this point you're more likely to start going through the stack trace if you have one to better understand the source of the issue. By now you have probably resigned to the fact that this going to take a bit more work to figure out than you had hoped but you've already sunk some time into it so might as well buckle up.
They key here is not to spend too much time reading through complex problems other people have had and make sure you're focusing on your issue. If after reading through your error message in detail and googling a bit more you're still stuck it's time to move on. Google is not going to help you and you're going to have to start thinking for yourself. This is the last level of easy, quick fixes and many developers are very reluctant to leave it.
Hopefully by now you've been able to admit to yourself that you really don't understand what's going on under the hood. The only way forward is to start to educate yourself a bit on the technology that you're using. This is a very powerful realization and most of your real growth and development as an engineer comes when you get to this phase of debugging. You are now forced to teach yourself something.
Of course, the easiest way to do this is to see if anyone else has written helpful or easy to use guides or references on the technology you're using. Sometimes the vendor or open source documentation is great and easy to read but often it's not and some other source will be an easier starting place.
After going through a guide or a deeper technical explanation you may realize that your bug is a result of a fundamental misconception you had about the technology. Maybe you discover places to look you didn't know about before, configuration settings you didn't know existed etc. You start to get a better picture of what's actually going on in your application. Better engineers will tend to speed past the lower levels and land here sooner. Sometimes, however, third party guides aren't sufficient and you need to look at the original docs.
The best engineers will head to the original docs far faster than their more junior counterparts. Reading detailed technical documentation is not always easy and there is rarely an explicitly stated answer to your particular problem. Instead the original documentation, written by the creators of the framework, package, open source tool, etc. will help you understand at a far deeper level what is going on in your application and how you should approach the bug.
More junior engineers are often intimidated by original documentation, even if it's very well written. It can often feel like you're moving farther away from the immediate issue you have to solve. It takes more work to study and understand the original docs. However, they are often the best source of information and will help you truly deepen your knowledge. What more skilled engineers realize is that taking the time to understand the technology makes solving problems far easier and is usually faster in the long run the the trial and error / aimless googling approach. Reading documentation is a skill.
In 99% of cases by reading the original documentation and really taking the time to understand the technology you'll be able to solve the problem. In some situations however the documentation has gaps and the only way to really understand what's happening is to read source.
If after reading the original documentation you still don't understand what's happening or why your program is behaving in a certain way you may have to go read the source code for the library, module, package, framework etc. that you're using. It is far more likely that you'll have to do this if it's a smaller project with poor documentation. Obviously this wont always be possible for closed source / proprietary tools but in those scenarios you'll usually have a customer support rep whose job it is to help you.
Learning how to read complex, alien codebases and figure out how they work is not easy but it is an incredibly valuable skill. This is the apex of debugging and it makes it possible to pretty much solve any issue. The tradeoff is that it can take more time so usually you'll want to review the documentation beforehand. The danger here is ending up reading through large swaths of irrelevant code.
It's really important to realize that the best engineers will be able to traverse these levels as needed, depending on the complexity of the issue they're trying to solve.
Just because you're a great engineer doesn't mean you won't quickly google something or run some trial and error tests to get more information. What makes them skilled is that they don't get stuck on any one particular level and know when to move on and do the research. It also means that their knowledge of the technologies they're using grows a lot faster.
Regardless, here's a couple tips to help you traverse the hierarchy.
If you find yourself troubleshooting some issue and you can't solve it within 15 minutes then start a timer for 15 more minutes. The main problem is that developers get stuck in levels 1 - 4 because they keep looking for a quick fix that will save them from having to do more research.
To manage your natural reaction set a time limit after which you'll stop googling around and retrying things and go start reading the docs / do a tutorial etc. Give yourself a block of time to do the research and see if you can solve the problem on your own. If after a designated period of time you still can't figure out what's going on then ask for help.
How many times have you stayed up late for hours trying to fix some bug only to come in the next morning and solve it in 10 minutes? This kind of thing happens to me all the time. Sometimes the best thing you can do is put some distance between yourself and the problem or just get some sleep.
Don't be afraid to ask for help. Be disciplined about how long you spend working on a problem but if after a certain point you can't figure it out then there's no shame in asking. If you've done the work to try and understand the underlying technology then when the answer comes it'll enhance your understanding.
Just make sure you understand the solution if you get it from someone else. I've seen multiple instances where some other developer with no context around the issue tries to offer a helpful solution and the developer who was in trouble in the first place took all their suggestions at face value. Then, as you might expect, the solution didn't work because the other developer didn't really know what was going on either.
The best way to level up your debugging skills is to build proficiency at levels 5-7. In the long run you will save the most time and become a better developer much faster.
Learning how to read technical documentation and source code to quickly understand how a particular library, package, module, tool, framework, service etc. works is very powerful. It will take time to build this skill but it will transform your career by giving you the confidence to solve even the most complex technical problems.
You got this!
If you liked this then you might enjoy my newsletter as well! I send out occasional emails covering interesting ideas I'm exploring, content I've enjoyed, and useful things I've discovered on my journey to become a better human.