Disclaimer — as I write this, I have been in the LaunchSchool core curriculum for just over a year and am nearing the end of the backend portion of the Ruby track. I am a student and will always be learning. This is what worked for me, maybe it will help you too — this was my route to passing the assessment, not the way to pass it.
This article has been inspired from studying with other students (you know who you are), which has really helped me clarify my thought processes and better understand how I break down problems.
Stop timing yourself
When I first started preparing for the interview, I was pre-occupied with ensuring I could solve problems within 20 minutes.
The issue with this approach for me was the sense of defeat when I could tell I wasn’t going to meet the deadline. Additionally, it isn’t easy to find problems that you can be certain are of the exact difficulty and length that you will get in the assessment. If you are tackling one that is a little harder, then it is absolutely fine to be taking longer than 20–30 minutes. However, when solving a significantly easier problem, then it really isn’t going to be ok to be taking a full 20 minutes to solve it.
Also, there is a difference between the difficulty of a problem and how long it takes to code one. Sometimes, it can be hard to come up with the algorithm, but then the coding may be quick, and so when you’re ready, this problem can be faster to solve compared to a problem for which it is easier to create the algorithm, but takes longer to code, just because of the nature of the problem.
I changed my mindset to that of training for a marathon (or what I imagine it to be — I have not, nor will I ever, run a marathon!). Rather than training by running marathons, I trained by running different distances. The metric I used for measuring when I was ready for taking the assessment, was having the ability to solve problems without struggling (too much — a little bit of struggle is fine and keeps me on my toes).
Obviously, this was only problems that I thought were in the same league as those in the assessment. Also, a minor refactoring of an algorithm, the odd failed test case or unexpected output was no cause for alarm. But, I aimed to feel in control of the problem-solving process.
I try to make my algorithms as agnostic of programming languages as possible.
‘Iterate through each integer in the input array….’
As opposed to:
‘Use each on input array….’
This keeps me focussed on writing an algorithm and not pretending to write an algorithm when I’m actually hacking and slashing code. It stops me putting blinders on and forcing the use of a particular method. This leaves me more open to the variety of methods that could be used, so that when it comes to coding, I can home in on the most appropriate method to use.
It also assists in identifying helper methods. These are methods that I wished already existed (and maybe they do — I only recently learnt of the #tally method from another student!) and will separate out into another algorithm.
As I solved many, many problems, I began to notice some patterns. Problems can look very different, but result in very similar algorithms. This led to me to develop some generic algorithms, for example:
- Find all possible combinations
- Select combinations that meet specified criteria
- Find optimal combination
This can be applied to problems such as: Find the length of the shortest substring from the input string, that contains exactly two vowels.
These may not lead to the most elegant solutions, and it almost certainly won’t lead to the most efficient solutions. However, they have the benefit of being widely applicable to a range of question types. As Sir Tony Hoare said and Donald Knuth famously popularised, ‘premature optimisation is the root of all evil’.
The benefit is that you may quickly identify a generic algorithm that can be applied. The time saved in figuring out which algorithm to use will be orders of magnitude smaller than the time saved by executing a more tailored and efficient algorithm when running it. You can come unstuck if the problem type has an exponential dimension (advent of code I’m looking at you) but this is unlikely to be the case in a 109 assessment in my (rather limited!) experience.
Back to square one
If the algorithm feels messy or if I’ve lost track of where I’m going or what I was trying to achieve, then I delete what I’ve done. All of it. I now have a scorched earth policy. I don’t archive it, move it somewhere else or try to rescue it in any way. By destroying it, I remove temptation. There is no way back and I must move forward. It means I am not constrained by trying to salvage something from the mess I created. If any of it really was any good, I can recreate it easily enough (this is not War and Peace, it’s just an algorithm).
If, after scorching the earth a couple of times, I’m still not getting anywhere, I simplify the problem to something I am more confident I can solve.
For example, if the problem is asking me to deal with punctuation as well as upper- and lower-case letters, and it is all too much, I create new examples limited to the lower-case alphabet. Or if the problem is asking me to deal with two input arguments, and it is seemingly beyond me, I set one of them to a fixed value.
Once I have solved the simplified problem, I can then gradually work towards the original problem, but at my own pace, bit by bit.
Test every line
Well, maybe not every line, but most. This is best done immediately after writing the line of code. This prevents nasty surprises later on. To be in control of your coding, you should know what is being returned by every bit of code and the best way to know that is to output it. If it is what you though it would be, great! Move on and write the next line. If not, now is better than any other time to fix it.
When I first started solving coding problems, I would write my whole method and then execute it. It would crash. I would fix the problem identified in the error message and then run it again. It would crash, again, etc. Eventually, it would produce an output that was not an error, but not the right output either. I would then use the #puts and #gets methods to narrow down what was going on inside my method.
This was a hard habit to break, but now I’ve broken it, there is no going back. I find it so much more efficient and satisfying to keep running my code with each additional line and to gradually increment my way to a solution.
Practice, practice, practice
I spent 4 weeks, after completing the 109 written assessment, preparing for the interview. I lost count of how many problems I solved. By the end of my preparation, I was doing 5–10 problems a day and it no longer felt exhausting (like it had at the beginning!).
After I had solved a problem, if it didn’t go smoothly, I deleted it all and tried again. I repeated this until I could solve the problem without (too much of) a hitch, and then moved onto the next problem.
This goes back to the analogy of training for a marathon (of which, I still know nothing about) — solving problems is not about proving to yourself that you are able to pass the assessment, it is about honing a skillset for problem solving. The more you practice, the easier it becomes to see similarities with previously solved problems. You can then draw on your experience and apply it to new problems. Having said that, I do think that LaunchSchool is particularly skilled in coming up with unique twists in assessments that may make you think about something in a way that you haven’t before. However, the broader your toolkit and the sharper your tools, the easier it is to keep calm and find something that works.
Don’t underestimate how tough this is
The 109 interview assessment is hard. This makes it all the more important to feel thoroughly prepared, knowing that you have well-practised skills and algorithms to fall back on when it is difficult to even remember your own name!