4 steps to solving any software problem

Problem-solving is a key skill for students, new programmers, and those who work with them.

October 18, 2017
Stepping (source: Skitterphoto)

I’ve noticed a gap in technical education which oddly coincides with a skill all software developers are expected to have: the problem-solving process. I started my software career with a combination of online tutorials and a coding bootcamp, but I’ve heard similar complaints about academic computer science programs.

I’m not saying no one formally teaches these skills, but it seems more common for developers to have to figure them out on their own. Many classic (and controversial) parts of technical interviews, like whiteboard exercises and “brainteaser” questions, are attempts to test these skills.

Learn faster. Dig deeper. See farther.

Join the O'Reilly online learning platform. Get a free trial today and find answers on the fly, or master something new and useful.

That’s why, whenever I’m helping beginners learn to code, I try to walk them through the process of solving problems in the same way I would at my job. I’d like to articulate those steps here, both for software newbies who are overwhelmed by this whole “coding” thing, and to see how it compares to the process other experienced developers use.

In general, I believe the process of solving a software development problem can be divided into four steps:

1. Identify the problem
2. Gather information
3. Iterate potential solutions

While I’m writing these steps with students and less experienced developers in mind, I hope everyone who works in software will find them a useful reflection on our development process. Programming instructors and anyone who mentors new programmers should make sure their students or mentees have a firm grasp of this process along with any specific technical skills they may need.

What kind of problem?

Note that when I talk about a software development problem, I mean a problem of any size and scope:

• You’re trying to do a very specific thing and you can’t get some piece of it to behave as expected.
• You’re seeing a strange error message and you have no idea what it means.
• You’re trying to figure out some cryptic section of a legacy code base when the original developers have all left the organization.
• You know generally what you want to build, but you have no idea what the individual components of the project will look like.
• You’re trying to decide what software package to use and you don’t know which one is best.
• You know there’s a function to do exactly what you want, but you can’t remember what it’s called.

Identify and understand the problem

This is easier in some cases than in others. Sometimes you get a straightforward error message and you realize you made a simple mistake: a syntax error, forgetting to pass all the variables you need into a function, neglecting to import a package.

On the other hand, sometimes the error is totally baffling. Often a bug won’t present itself with flashing red lights—the program just doesn’t do what you want it to do.

Even when this is the case, you can try your best to articulate the problem. Ask yourself the following questions (and maybe even write down the answers):

• What am I trying to do?
• What have I done already?
• What do I think the program should be doing?
• What is it actually doing?

Gathering information

Sometimes I see people skipping straight to this step without having done the previous one. Examples include:

• Googling Stack Overflow as a first step.
• Copying and pasting code—whether from Stack Overflow, a tutorial, or elsewhere in your codebase—without understanding what it does.

I believe this practice leads to “solving” problems without fully understanding them. That’s not to say any of these resources—Stack Overflow, tutorials, any other examples you find—are bad. But they should be treated as a single tool in your toolbox, not the start and end of the problem-solving process.

How else can you use this toolbox? Think about the kind of information you’re looking for:

• If you know exactly what function, class, or API endpoint you’re using from an external package or service, you can go to the relevant page in its documentation to see all the various options when using it.
• If you’re having problems with an open-source package and you don’t know why, try reading the source code for the relevant feature to make sure it’s doing exactly what you assume it is.
• To get an overview of a new tool or framework, try searching for a tutorial or quickstart guide.
• If you don’t understand why something in your code base is designed the way it is, try looking at the commit history for the relevant file or files—often you can piece together a story of what past developers were trying to do.
• And yes, search engines. Sometimes you know exactly what you want to do but you don’t know what it’s called: “PHP assign two variables.” Sometimes you want ideas on how to do something: “JavaScript shuffle a deck of cards.” And sometimes you just have no idea, but looking at other people’s similar problems can help you figure out what to try next: “Django forms validation not working.” When you do this, try to read any links or relevant documentation you find to get a broader understanding of the issue.

If I’ve been using one of these methods for a while and I don’t seem to be making progress, I’ll often switch to another. I find that a lot of developers I know reach for the search engine first, but for me, intentionally using a variety of methods helps me gain a broader scope of understanding.

Iterate potential solutions

Try something. It doesn’t have to be perfect. If you see anything change as a result, that’s a success. You’ll improve on it soon. Then keep trying things until you’ve made substantial progress on the problem.

If you’re in unfamiliar territory, it can help to break down the “solution” into very small increments, and try them out piece by piece. Print your data to the console before you worry about how it’ll be rendered. Call a function you haven’t used before with simple hardcoded arguments, and get it to run as expected before replacing them with the actual data you’ll be using in your application.

This still applies if you’re using someone else’s code from Stack Overflow or a tutorial as an example. Don’t just copy and paste the code into your editor—type out the code line by line. This has two advantages. First, you’re forced to engage with the code and understand it in more detail. Second, you’ll have the chance to update anything that doesn’t translate perfectly to your application. Maybe you can leave out a variable you won’t use; maybe their example uses class Animal and you’re trying to sort Books, so you’d replace a variable called species with one called title.

Sometimes it’s harder to try out what you’re doing after every line of code; that’s ok. The idea is to avoid a situation where you’re typing away at code for hours, only to find that what you created doesn’t work and you have no idea why. Try to find a middle ground, and get to results you can see within a relatively short amount of time.

If you iterate like this for a long time and don’t seem to be getting anywhere, maybe it’s time to start back at step one and try something different. But if you can get something to work, even if it’s not exactly what you had in mind, now’s a good time to move on to the next step.

Often we do this by hand: load a web page and check that it contains all the elements we expect it to render. Try replicating the conditions that led to a bug, and confirm that the bug no longer happens. Try using the feature we added in a few different ways and see what happens.

Another way we do this is with automated tests. Adding a test that asserts a feature works as predicted or a bug no longer occurs helps prevent unexpected problems down the line.

Test-driven development is an alternate approach that starts with this step rather than leaving it to the end. For each change you make to your project, you start by writing a test that asserts the change will work as predicted, then make the change.

One advantage to the test-driven approach is that it forces you to think about what success means before you start working on a given section of the project. This is a good question to ask yourself whether you start by writing a test, write one at the end, or verify your change worked by some other means. It’s part of the first step defined here—identify and understand the problem—because it’s so fundamental to finding a solution.

Even if you are writing automated tests before you add any program code, you’ll be checking that a given portion of work satisfies what you’re trying to do: running the test suite, and trying the feature to make sure it works as expected.

What’s next?

These are the steps I take to solve problems when coding, and the ones I try to impart to students and junior developers when I’m helping them with an issue. I’d like to see more coding education programs—whether in academic computer science, bootcamps, or self-paced tutorials—provide their own instructions on this process. The exact process will depend on the person, the organization, and the work they’re doing—but knowing how to solve problems is a foundational skill to being a programmer. If you work with students or less experienced developers, see what you can do to help them develop this skill.

Post topics: Software Engineering
Share: