Over the last decade I seem to have been working in environments, where many engineers and engineering minded people spend time with programming puzzles and coding challenges. Let it be Advent of Code, Project Euler, Exercism, TopCoder, or Leetcode. I’ve tried all of these before (and probably a few more that I no longer remember), though with various amount of time spent all fired up, and then fizzled out. Recently I’ve picked up Leetcode, since from the above list that’s why I’ve spent the least amount of time with and others mentioned using it a way to relax and learn on weekends (suspend judgement on the wisdom of that for now).
Thus in the last two weeks I was solving problems, though not just any problems, but went in mostly for the Easy ones. These few dozen problems and short amount of time doesn’t give me a deep impression, but from past experiences I can still distill some lessons that help shaping future experiments.
The purpose of using the Easy problems is different from e.g. going all in for puzzle-solving fun, which is likely in the Hard ones. Rather than that, I think easy problems can be used for learning some new techniques, looking for common patterns, and becoming more polygot.
New Techniques & Common Patterns
It’s 100% that I’ll be able to solve the Easy problems by myself (otherwise I should give back my nerd card). On the other hand, there are always more than one way of doing things.
For example, many Pandas problems on Leetcode could be done with various “merge” or “join” application, or oneliners versus intermediate variables. It’s fine to know one, but then checking the solutions by others, and finding the alternate ways, trying them out, and seeing their tradeoffs.
Another kind I’ve encoungtered, that’s maybe only new to me, but illustrates things, is solving certain problems while iterating one way (incrementing counters, from left to right, etc…), while in reverse (decrementing, right to left, etc…) the solution might become neater or more obvious.
After solving the problem one way, just going through others examples, looking for options that I haven’t considered, and pitching them against what I did originally definitely expands my toolkit. In addition since these are easy problems, it affects more common situations, code that I would encounter day to day.
Skipping through the other shared solutions I can also get to see common patterns. It’s a bit like statistical mechanics that while among the solutions there’s loads of crap1 but on average the patterns of solutions appears that is relevant for a given language.
Given that most Leetcode problems are allowed to be solved in multiple languages, that supports learning across languages very well. For example Pandas problems paired with solutions in MySQL: sometimes the pattern of solutions were very similar in the two, other times needed very different thiking to get to a solution that felt “right”-ish. Similar case in the other combination of Python and Rust that I’m trying to dive into.
The trick is to try to solve things in any way at first, and then do the previous “look for techniques and patterns” to build up the experience.
Using Easy problems in this case gives a lot of different, simple use cases where the basics can be compared easier, focusing on the language similarities and differences. The problem difficulty then doesn’t muddy the waters.
Build Up Programming Muscles
The Easy problems have smaller scope (that’s why they are easy), and seems to allow improving pattern recognition, on which other, more difficult problems can build on. It’s all about building shortcuts based on encountering similar stuff and making the connection, and building chunks that can be reused later.
Most of the day-to-day programming likely encounters these sorts of easy problems anyways, thus my hypothesies that they can be good bang-for-buck for improving baseline effectiveness. Of course if someone always works on big-hairy-problems, this would stand. But how common is that?
Flipside: Why Not Do This?
The incentives at Leetcode doesn’t seem to align towards quality overall. When developing for efficiency, the run result timing seems to support that but the variance is way too high to be really useful. The solution sharing and feedback also seems to support that, but there’s way too much noise and broken feedback looks to be effective at that. Thus the quality of the above mentioned steps really depends on how much effort I put in there. It’s very easy to grind (get a lot of problems done)2, but learn almost nothing, or learn the wrong patterns.
Wrong patterns is indeed the main culprit. The problems are not set up to be “production quality” that would make one a good engineer. Rather than doing things in a clever way, or going in for the oneliners at the expense of any readability, or using a solution that is more hard-coded & tailored to a narrow case rather than with a bit nicer way that generalises, etc…
Most of the time I also cannot even really remember the solution from even an hour ago, probably the side effect of going for quantity (easy) instead of quality (hard problems).
Leetcode is definitely looks like a reasonable tool to have in one’s kit, though not as the main one. I think I’ll do look around more among the Easy problems to see more patterns, but for more interesting ones I’d go gradually up, otherwise I’d just drop it soon enough.
On the other hand, the more useful step might be picking up my learning paths on Exercism, where a few things are diametrically opposite: more quality focused, direct feedback from knowledgeable people, and empracing iterative improvement. It seems less polygot (there are many languages, but they are learning paths independent from each other, even if some problems do repeat between them), but that’s not a showstopper.
The main thing is, though to continue a habit that is created by this experiment: deliberately practicing programming, seeking out alternatives, and not taking them at face value.