“P” is for Perspective

Nirab Pant
8 min readOct 10, 2021

--

(A post-assessment reflection following the first set of assessments as part of Launch School’s mastery-based curriculum on the fundamentals of software engineering)

Photo by Joel Fulgencio on Unsplash

In this article, I briefly recount some pitfalls when tackling the live-coding interview but also with problem-solving in general.

In particular, how do you recover when you think you have a fail-safe strategy to solving a problem only to realize, having committed significant time and thought in the implementation, that there is a possibly catastrophic flaw in your approach?

The latter half of the article discusses an example coding problem to consider how the initial impressions we formulate in conceptualizing a problem can bias the approach we take in solving it.

Finally, I suggest a meta-PEDAC (or pre-PEDAC) problem-solving approach that makes a conscious effort to acknowledge the default mental model we may have before putting it aside to consider alternative approaches.

If you don’t know what you’re coding, you’re probably not gonna code it

In essence, this article is about choosing the right problem to solve, and my reflections on how the “PEDA” in the PEDAC process is really about gaining perspective on the problem. PEDAC stands for “Problem Specification”, “Examples and test cases”, “Data Structure”, “Algorithm” and finally, the “Code implementation”.

Typically, the PEDA aspect of PEDAC is an iterative process where the programmer first clarifies the main requirements of the problem, taking time to comprehend the example test cases provided or writing new test cases as appropriate.

Importantly, the actual implementation is separated from the solution logic. Only after having a clear problem specification do they then expend effort in developing the relevant algorithm. After all, to misquote the timeless wisdom of Yogi Berra, “if you don’t know what you’re coding, you’re probably not gonna code it.”

Is the “mental model” a help or hindrance?

However, there is a precursor step to PEDA that is often overlooked: the mental model that we’ve formulated of the problem, almost automatically, when we are first deciphering the problem statement.

This is important because when we first set out to solve a coding problem, we typically have an initial intuition or inclination to choose a particular approach, and this can heavily bias the direction we take in the rest of the problem.

For example, what would be your first thoughts after reading a problem that involves transforming a string? Perhaps you would start thinking of certain regex patterns that could be used, or visualize a series of chained methods converting the string to an array, followed by one or more of the array iteration methods (map, reduce, filter), and finally a join() to return the transformed string.

This intuition is increasingly refined with experience as we start recognizing certain patterns in the code we write, similar to how, say, chess grand-masters can recognize patterns on the board without explicitly enumerating all the possible moves and the trade-offs of a particular sequence of play.

The trouble here is that when you follow the PEDA process and start crystallizing that initial intuition into pseudo-code, you’ve dramatically constrained the solution space for that problem. It also becomes increasingly difficult to mentally context-switch and consider the problem from another angle the more you invest in mapping out a particular approach. To coin a term, you become cognitively-committed to a particular solution and further elaborating on the PEDA steps is effectively a form of premature optimization.

The example below should help clarify how this rather subtle idea of our “mental model” or “problem intuition” can have an outsized effect on our problem-solving approach.

Example problem solved using PEDAC + “initial intuition”

Here is an example coding problem featured on one of the example JS 109 live-coding videos. I will first describe the approach I took and then compare it to an alternate solution in the next section.

Create a function that takes a positive integer and returns the next bigger number formed by the same digits.

Most of us will probably require at least a couple readings to glean a clear sense of what the problem is asking us to do. If some example test cases have not already been provided, we would also write a few of our own at this point, a notable step in the “E” aspect of PEDAC.

For example, given the positive integer 1234, the next number that can be formed with these digits that is bigger than the original number is 1243. Here, the situation was fairly simple and served to further clarify the problem statement.

However, almost immediately after understanding the problem, I would have formulated that initial intuition of what I need to do: “Find every possible permutation of integers using these digits, order them, get the current position of the initial number, and the solution will be the next number in the array.”

I describe the above as an “immediate initial intuition” because it really is precisely that, a non-verbal sense of what needs to be done, and not at all a process of thought which is what PEDAC entails.

Typically, following on with the PEDAC process, we could further clarify the bounds of the problem, for example, if we have repeated digits, do we ignore these and jump straight to the next bigger number? Also, can we have leading 0's? And what if we already have the largest possible number using those particular digits?

That’s all fine but, meanwhile, in the back of my mind, that initial intuition continues to grow and crystallize and by the time I reach the “D” and “A” steps of PEDAC, I am effectively thinking of what data structure(s) and algorithm to use to bring that initial seed idea to life.

In this case, this is what my code solution looked like (following PEDAC to elaborate the necessary steps):

Here, the solution involved a recursive function getNextDigits to create the tree of permutations to get all possible positive integers from the digits, and another function getSortedUniqueNums to extract only the unique numbers, and finally, the main function nextBiggerNum to return the, well, next bigger number.

Code analysis and comparison to alternative approach

This code works. It was deliberately developed and written using a formal methodology. It’s also fairly organized (in my humble opinion) with each specific function of the code, e.g., getting all possible numbers from the original digits, parceled off into a helper function that can be separately tested before combining their functionality in the main function. It was also not that trivial (for me) to get the recursion working but I was able to get the code functional within the allotted 30 mins (typically two coding exercises in an hour for the assessment).

However, the code breaks for any input greater than 12 digits due to the large memory footprint incurred by the recursive function calls as can be seen in the figure below.

The code runs for approximately two minutes before the program runs out of memory when using the recursive approach. Note that a result is returned with 11 digits but the program crashes with 12 digits.

Furthermore, in the process of implementing this code, from iteratively laying out its various components using PEDAC, to debugging any issues, I simply did not have the cognitive capacity to conceptualize a different approach.

On paper (or at least in pseudocode) the implementation logic is valid, and indeed, the code yields the correct response for test cases of 11 digits or less. Without an hour-long time constraint hanging over my head, I could have probably put this approach on hold to start exploring alternative routes, but that’s hard to do when you’re already 20–30 mins into the problem. Besides, there is no upfront guarantee that other approaches would be any simpler.

Thus, the question is how to modify our approach so that we perform, in a sense, a meta-PEDAC?

Can a formal approach be developed that will acknowledge the initial intuition we had but, rather than immediately beginning to outline the algorithm for this mental model, put it aside and consider different approaches?

In the featured video solution to the above problem, the student (after a little prompting from the TA/examiner) was able to think of an approach that increments the initial number by 1 and then checks if it has the same original digits. The final solution was similar to the following:

This is a completely different approach to the one I took, namely, to enumerate every single permutation of the number and order them to get the next one in line. Importantly, this alternate solution would have never occurred to me while I was figuring out how to implement the default mental model that ‘spontaneously’ formulated in my mind.

With the luxury of hindsight, I can compare the two approaches. For example, while the brute-force, increment-by-1-and-check-if-the-digits-are-the-same, has a steady memory footprint, it can get very slow when you need to increment the original number by a lot to get the next bigger number. In fact, here is a visual of the steep drop-off in my computer’s CPU usage when the function finally finished running the input 12333333333 to get the result 13233333333.

Note how the memory is more-or-less a straight horizontal line throughout while CPU usage decreased after the code finished running the brute-force solution. This is in contrast to the increasing memory costs of the recursive approach.

Nonetheless, slow as it may be, it does eventually return the result, even for input numbers of 12 digits or more. On the other hand, the recursive method reaches a memory bottleneck and crashes for inputs of 12 digits or more as we saw earlier.

Conclusions

The main take-away from the above discussion is that although the temptation is there to just run with the default intuition we have of the problem, especially when working under a time constraint, it can serve us well to put that initial approach on hold and brainstorm alternative angles to tackle the problem.

We would essentially be performing a PEDA-lite on each different approach to understand not the just the problem but the potential solution. Then, depending on the ease of implementation, or perhaps performance requirements, we would select a particular approach.

For example, in the problem considered earlier, I would have acknowledged my initial mental model of “enumerate all possible numbers” then briefly considered what that entails using a high-level PEDA overview. I would then momentarily put this aside to free up the mental capacity to consider alternate approaches I could take to solve the problem.

Finally, only after having considered the merits of both (or more) approaches, would I then resume the formal PEDA strategy of outlining the pseudo-code required before coding up the implementation.

Perhaps one source of the difficultly I encountered with the above problem is that, ultimately, a more rigorous algorithm development strategy would consider the time- and space-complexity. Furthermore, one would hope that I would be sufficiently acquainted with a wider mental library of data structures and algorithms and coding experience to better intuit which would be the right approach for a given problem.

Nonetheless, for now at least, the main lesson for me is that spending sufficient time with PEDA is ultimately about getting a better perspective on the problem: not just understanding the problem and how to solve it, but also choosing what to solve.

--

--