This week’s article comes from our partners at Outco.io
It’s a hierarchy that consists of different levels of mastery over a subject. To move up, or operate within a certain level, you need to be able to perform certain tasks or solve certain kinds of problems. As you move up to higher levels, those tasks become more complex, not just because they build on mastery over the previous levels, but because they involve “phase-shifts”.
These phase-shifts are the “aha!” moments we all experience when learning a new subject. Where stuff just clicks and all the pieces seem to come together. Where there’s a distinct “before” and “after.” A one-directional paradigm-shift, where you take a step back and can no longer see the world the same way. One nice thing about this process is that as you move up to higher vantage points, you’ll be able to reflect back and see the limitations of past perspectives. And if you can communicate how you navigated the climb up the Bloom’s pyramid, then you’ll be able to help others walk the path too. Which in turn, helps your own learning.
Recursion is a recurring theme that I write about (some pun intended). Being able to take a step back and analyze a previous state can be valuable, whether that’s while learning, or in a computer program. And at the risk of becoming a little too meta, I’ve decided that recursion would be a great example to use for this blog post to illustrate the different phases of the climb.
The first step in learning something new means being able to memorize, define, repeat, and recite. It’s how most of us learned our multiplication tables growing up, and why we used flashcards to study from multiple-choice tests. It’s basically just pattern-matching, and though it’s a shallow form of learning a subject, it’s still a necessary first step.
At this level, you’re learning basic terminology. So using our recursion example, this would be knowing that a recursive function is a function that invokes itself in its own body. It’s knowing that such a function is made up of a base and recursive cases. And it’s knowing that it’s often used in iterating through branched data structures, like trees and graphs.
Memorizing these kinds of succinct definitions can be useful in interviews in case the interviewer feels like throwing a few trivia questions your way. You want to be able to rattle off answers quickly and concisely to show your understanding. But just memorizing information isn’t enough to pass coding interviews, and that’s one of the things that make them so challenging. You have to actually know how different topics are connected to one another.
Knowing is not the same as understanding. This is more than just a semantic difference. For starters, you might look at this problem statement and know what every word means, but not understand what the problem is asking:
And if you don’t understand what the interviewer is asking you, there’s almost no chance of you accidentally stumbling across the solution. It’s one of the first things I stressed to engineers while I was an instructor at Outco. And it’s not always a trivial matter to figure out. Sure, the first step is understanding inputs and outputs. But then you have to understand the roadmap of how to get where you want to go. You have to understand why it might be a challenging problem, or what inputs might break your assumptions, or why the interviewer wanted to ask it. There’s always more to unpack about problems when trying to understand them than what’s given in the problem statement.
If you want to go a little deeper down the rabbit hole, Vsauce has a good video where he breaks down the meaning of the word “understand”: He relates understanding to being inside of and surrounded by something. That something could be a set of topics, ideas, or concepts. Defining understanding this way helps explain why we use the words “immersive” to describe Outco’s interview prep program or we say we’re “diving into” a topic like recursion or dynamic programming.
So if I gave you some code to solve a recursive problem like the Nth Fibonacci Number, mastery over this level means you’d be able to identify which part of it is the base case, and which part is the recursive case. You understand why the base case goes first, and why you don’t need an else clause to your if statement. And most crucially, you understand why you might want to set this problem up as recursive function: it comes down to solving smaller versions of the same problem and combining those solutions.
To me, understanding is all about drawing connections between facts. It’s “connecting the dots.” It’s seeing how all the different pieces of the puzzle fit together. And it’s using different lenses to see the world. But understanding how the pieces fit together isn’t enough. You have to actually solve the puzzle.
In coding interviews, this is probably the most crucial step that you are being tested for. Being able to apply what you know is pretty self-explanatory. But, how can you tell if you’re actually ready to apply your knowledge and understanding? What is a clear test you can give yourself? Being able to look at the solution to a problem and identify what each part of the code does and how it works is a good indicator of being at the previous level of understanding. Being able to look at a new problem statement in a given domain and getting to a solution that can pass a set of predefined test cases is a good indicator of being at the level of applying.
It’s a tricky thing because understanding the code someone else wrote can give a false sense of mastery. It can give you the false belief that you’d be able to come up with that same solution on your own after reviewing the answer once. While it’s good to review other people’s code and to try to understand it deeply, the only way to really know if you’ve mastered a subject is to try applying it to new problems. You don’t want to just know how to solve the Nth Fibonacci number, you want to be able to solve any recursion problem that you encounter in the wild.
This creates the challenge of needing to then find new problems, that have been pre-sorted into a particular category you’re interested in. Just understanding that there even are different categories of problems presupposes a certain level of mastery. This is where the structure of a curriculum comes in handy. A good one organizes the set of all available problems into a progression of topics that build on and reinforce one another. It’s something I spent a lot of time working and iterating on while at Outco.
To some extent, mastering this level just requires doing and seeing a lot of different problems, and in different contexts: whiteboarding, timed challenges, longer-form homework problems, etc. But I think it’s valuable to note that what’s actually happening when you learn how to apply a concept like recursion, is that you’re simplifying down the essential components of that problem. You’re trying to turn it into a game of plug and play, where you have some blueprint of how to solve the problem readily available in your mind, and now it’s just a matter of filling in the blanks and accounting for the quirks of that particular problem.
All recursion problems have a base case and recursive cases. What are the conditions for each? And are we aggregating some return values at the end of the recursion or are we picking some optimal choice of return value? Does this follow a pure recursion pattern, or a helper method recursion pattern? When you’re able to apply what you know, new problems start to become instances of a single pattern with small variations. And the chaos of the world gets reduced down to a set of blueprints.
However, just because you can arrive at a solution, doesn’t mean you can’t arrive at a better solution.
What makes one approach better than another? What are the factors that we need to consider to make that determination? Is it the number of lines of code we used? Is it the number of external libraries we reference? How do we take into account code reusability and readability? How important are things like variable names or code cleanliness and indentation? There are definite tradeoffs between cleverness and clarity:
To me, analysis is all about being able to translate one way of looking at information into another useful way of looking at it. For recursive problems, it’s knowing how to diagram and extract useful meaning out of the diagram.
Ready more at Outco.io