Welcome to Rithm’s series on problem-solving strategies. If you’re just joining us, you may want to start at the beginning. Here’s a list of the articles we’ve written:
- Understand the Problem
- Explore Concrete Examples
- Break It Down
- Solve a Simpler Problem
- Use Tools Strategically
- Look Back and Refactor
In How To Solve It, mathematician George Polya breaks the problem-solving process down into four pieces: understanding the problem, making a plan, executing the plan, and reflecting on the solution. We’ve already talked about understanding the problem, but the next parts of this process could benefit from a little unpacking.
Whether you’re building a new feature for an application with millions of users, or in the middle of a whiteboard interview, it’s essential that you have a plan before you start coding. But planning often requires forethought and insight that comes from experience. Because of this, beginners often feel trapped in a sort of catch-22: they need experience in order to formulate plans effectively, but they need to plan effectively in order to solve problems and gain experience!
Strategy #2: Explore Concrete Examples
When it comes to coding, one way to help formulate a plan is to explore concrete examples. Before starting to program a solution to your problem, make sure you have plenty of examples of how the solution should respond to different inputs. For larger features, these examples may be codified as user stories. For smaller pieces of functionality they may correspond to unit tests. In either case, having a clear understanding of the solution to the problem in many concrete cases can not only help develop your understanding of the problem, but also provides sanity checks that your eventual solution does what you initially thought it should do.
An Example
Imagine you’re in an interview and are asked to do the following:
Write a function which takes in a string and returns counts of each character in the string.
Once you feel like you understand the problem, a good check for your understanding is to propose a few different examples of the output that this function should produce for a given input. Here are some helpful things to keep in mind when formulating concrete examples:
- Start with simple examples. If you can’t accurately predict the output for a simple input, this means you don’t understand the problem yet. Here are some simple examples for the problem above:
charCount("aaaa"); // {a: 4} charCount("hello"); // {h: 1, e: 1, l: 2, o: 1}
Even in these simple examples, clarifying questions arise. For instance, should the return value include keys for every character, with counts of zero for characters that don’t appear? Or should it only include counts which are strictly greater than 1? The example above assumes the latter, but it’s an important part of the output.
- Progress to more complex examples. Once you’ve explored some simple concrete examples, ratchet up the difficulty and see if your understanding can keep pace. This process will often reveal other questions. In the problem above, for instance, what should the output be for an expression like
charCount("Your PIN number is 1234!")
? Should the counter only return counts for letters, or for numbers too? What about non-alphanumeric characters? Should it distinguish between uppercase and lowercase letters? Being able to solve the problem in this specific case is critical to planning for a general solution. - Explore examples with empty inputs. You should be sure to explore edge cases as well. Oftentimes edge cases will be specific to the problem at hand, but there are a couple of general principles. One is to explore what happens if you provide an absence of input into the problem. For example, what happens when you call
charCount("")
? Should the output be an empty object?null
? An error? - Explore examples with invalid inputs. Another helpful class of edge cases comes from considering what happens if you pass invalid data into problem. For example, what happens if you put something which is not a string into
charCount
? What’scharCount(null)
?charCount({key: "value"})
? Understanding these edge cases may not help you solve the core problem, but they will help you develop a more robust solution.
After you’ve explored several concrete examples, it’s time to tackle the problem in general. For that, you may want to make use of another problem-solving strategy. We’ll discuss this one next time.