Capstone Week 1: The Power of a Good Mental Model
The first week of capstone is coming to a close. The contrast between how I approached problems just 6 days ago and now is astonishing… and it’s just the beginning. With the sheer amount of learning that came out of this week, it is valuable to iterate over it via writing for the sake of retention. So, here is a distillation of some of the concepts I found most important.
1. Translating a mental model into code is the easy part…
…And constructing a model that accurately represents the problem is the most difficult part. In fact, if I read someone’s code, I can usually reverse engineer the mental model they used to solve the problem. However, if I were presented with that same problem, it would be far more difficult to go from the problem description to the mental model that their code represents.
On Friday, we did mock interviews with the other members of our cohort (there are 6 of us in total). One of the students spent 33 minutes developing a mental model and only 7 minutes coding the optimized solution. Why did he do this? Because it is far more important, and far more challenging, to construct the proper mental model than it is to translate that model into code. Constructing a mental model is an act of problem solving, of creativity, and of synthesis. Translating it into code is just a matter of being fluent in a particular scripting language.
2. Not every solution is a good solution
Some solutions are downright bad. Getting code to work and getting code to work well are two very different feats, and the latter is far more challenging. There are multiple trade offs to consider when solving any problem. There is the classic trade off between space efficiency and time efficiency. For example, I encountered a problem which I could solve in O(n log n) time, using O(1) space, but it could also be solved in O(N) time with O(N) space. The former is a little slower but more space efficient, while the latter is a little faster but less space efficient… So, which one should I choose?
Which brings me to my next point about constructing good solutions: Your solution doesn’t have to be a good one at the start of solving the problem, it just has to be a good one by the time you finish. We tend to start out with inefficient solutions — “naive” or “brute-force” solutions — and then we “push our solution down the big-O curves”.
Starting with the naive/brute-force approach is not only fine, it’s beneficial. The naive approach is usually intuitive, but to come up with it still requires a sufficient understanding of the problem. I.e., it allows you to focus on understanding the problem rather than on constructing a clever solution. It also explicates the performance bottle necks, which are the areas to hone in on during optimization. So, the naive approach provides tangible next steps. It is the difference between trying to jump 10 feet ahead of you in one go versus jumping 5 feet once, then 5 feet again. The latter is far easier.
3. Reading something is very different from understanding it
Another thing I really appreciated about this week was having an expert present and attentive every step of the way. Prior to entering capstone, students were required to read a book on data structures and algorithms. I read the book more than once and I felt that I had a strong intuitive grasp on many of the concepts…
And then it came time to apply those concepts.
Sure, I understood the theory behind Big-O and behind the various elementary and compound data structures. I even dove into the mathematical proofs related to Big-O just out of curiosity. But being able to notice or anticipate performance bottlenecks is a whole different ball game. My perspective of problem solving has changed dramatically. Prior to starting capstone, I was satisfied when my programs produced the correct output and were written well (i.e., no code duplication, proper naming, modularity, etc.) Now I see that that is not enough: Performance is a consideration every step of the way… But not at the expense of other important design factors. In other words, performance is an important added consideration, but a good software engineer focuses on all facets of constructing a solution. A good solution is not only coded well, easy to read, consistent in its level of abstraction, modular when it needs to be, but it is also performant. All of these considerations are important.
4. A few mental models map to many different problems
Learning to skillfully and quickly develop a mental model for a problem has a steep learning curve and I am still at the base of that curve climbing my way up. There are some things I have already noticed, though. Algorithm and data structure problems abound, but the mental models that can solve those problems are less abundant.
Our instructor is constantly reminding us to focus on the mental model. When we review problems, we don’t even look at code until we have sufficiently and clearly discussed the appropriate mental model as well as the line of reasoning we followed to come upon that mental model in the first place.
As I solve more problems, I am beginning to notice the similarities between many of problems that are ostensibly very different, but fundamentally very similar. Even today, I spent a couple hours on a few problems, trying to flesh out my mental models for them, only to arrive at the same mental model for all of them. Consequently, the code I wrote to solve each of those problems looked almost identical. This was in spite of the fact that each of the problems’ descriptions sounded entirely different. That mental model is seared into my memory, as is its problem-pattern counterpart.
The real beauty of this approach is how much easier it is to approach a problem once you recognize the general pattern that the problem falls into. If you have a mental model that matches the pattern of the problem, then there is actually very little intensive thinking, or problem solving, required: You already know the solution, it’s just a matter of translating that solution into your language of choice… Which, as I said before, is the easiest part.
Capstone is everything I could have wanted out of a professional training program… The conversations are rich because each individual is:
B) very hard working
C) already proficient developers
D) respectful and inspired to learn
Because of those factors, the conversations between members of the cohort are also great learning experiences. We all have a pretty high baseline of knowledge because we have completed the robust mastery-based software development curriculum. So, we are able to focus on higher level topics without getting derailed by confusion about fundamentals. This allows us to focus on topics that are more engineering-centric.
Finally, the instructor is extremely skilled at distilling complex ideas. Some of the grandest learning experiences I have had were during our instructor’s “walkthroughs” of the problem solving process for a particular coding challenge. He takes us from the problem description all the way to a fully-optimized solution. He explicates his thought process so that we understand how to go from a problem description to a mental model. If he started with the mental model itself, we would never be exposed to that pre-model process of deriving the model in the first place. Yet, this is the most crucial step in solving a problem. Mental models are easy to understand if they are put in front of you. They are not easy to come up with. He does a great job of teaching us how to come up with them. I still find it very challenging, but I am a lot more comfortable with the process than I was a week ago, and I’d say that is a pretty dramatic shift in a pretty short amount of time… 2.75 more months to go… back to studying (yes, Capstone students work on Satudays).