In the spring of 2015, my manager in London told me he was impressed with my technical breadth. I joined his team after a hackamonth working on a code editor with Kent Beck, went on to work on some low level bluetooth routing, and eventually left that to work on the newsfeed and composer surfaces of the Facebook app, covering three quite distinct surfaces in a few months. That conversation was the first time when I really felt like a senior engineer. Senior not in the sense of “I achieved level 5”, but rather in the sense of “I feel like I know what I’m doing”. Having worked on a lot of apparently unrelated things, I finally achieved a sense of mastery. This post offers some introspection on that process.
(The other reason to write this one is that I really value symmetry, and thus a post about Depth keeps wanting its counterpart to exist)
I don’t necessarily recommend that you become a generalist — it probably takes longer to rise up in the ranks than it takes a similarly skilled specialist. Furthermore, generalists tend to eventually branch off of technical jobs and go into management or other less technical disciplines. But some people — like me — can’t help themselves, and will sooner or later get bored of encountering the same problems, or worse, knowing the solutions and slowing down on their learning curves. At that point, the natural thing to do is simply to seek something new and shiny again. So that’s element number one of being a generalist: you don’t want to specialize.
Now, how does anyone get good at anything if they don’t put in enough work? I don’t think they do. If I kept switching to completely different jobs (say farming, baking, running, painting) every 2 years I’d probably be mediocre at best at each of those jobs. The key realization for one to become a successful software generalist is that things are largely similar and applicable across different domains.
As obvious as it may sound, it turns out that it’s all just code. At a high level, it doesn’t really matter if the code is running on a desktop PC running Windows, or on an Android phone, or on a k8s container hosted on god-knows-which-cloud-provider. Code, as Turing would teach us, is all some arithmetic, branches, loops and state. And to make things even easier, modern system architectures are very similar to each other. Armed with that knowledge, one can always go ramp-up on a new problem space as long as they can patiently read and understand the code! I can’t emphasize enough how important it is to read the code. I’ve found and fixed a lot of issues over the years just because I’d be the only one to actually go read the code very carefully. A corollary here is that programming languages don’t matter all that much — as long as you learn what the computer is doing behind the scenes, you can always figure out how to work in a new language.
The other important property of software that helps generalists is that certain patterns are fractally applicable (i.e they help at a high level, but if you zoom in and look again you still want the same patterns to hold). An obvious such property is the minimization of dependencies (in particular circular ones). You want this to be true when designing a collection of services, but also when defining a collection of processes in your container, or a collection of classes in your process, or a collection of variables in your class. Another one is size, or partitioning. No matter which level you’re operating at, you never want something so big you can no longer reason about it (think a 1000 loc function, or a service with 1000 endpoints), and you also don’t want a collection of pieces so small that you lose track of what exists (think 1000 functions made of 1 line each, or 1000 tiny services).
I don’t intend to create an exhaustive list of all the behaviors that help across software domains, but some others include “testing” (how can I know something works), “fast iteration” (let’s build things in small chunks and ship them ASAP), “customer focus” (who are we building this for) and “business focus” (why are we building this). As you move through different projects in your career, these meta skills continue accruing and remain applicable to everything you touch. That’s what allows one to keep on improving when switching over to a new area — despite it being new, they’re not starting from scratch.
One final thought. While my intention was to create a counterpart to my depth post, I now realize that these two have a common thread: curiosity and its natural consequence, exploration. No matter whether you’re going deeper into something you’re familiar with or getting to learn something brand new, allow yourself to be curious! Don’t treat software as black boxes. When given a chance, open it up and see what’s inside!