Of the mental models and rules I use in my life, by far the most useful is to learn only one thing at any given time.

If you get it right, you:

  • Ensure work is the right balance of challenging for it to be enjoyable.
  • Avoid stalling due to an overload of uncertainty.
  • Learn better, and faster.

This rule works for individuals but is also useful for coaching others, debugging team performance, or helping people grow.

As an individual

One of the reasons people find learning new programming languages hard is because they don’t follow this rule.

Take learning Rust, for example.

Excited to learn the language and get building, what could possibly be a better learning side-project than implementing the Raft algorithm from the whitepaper?

Well, in order to implement this, you must:

  • Have a strong theoretical understanding of the algorithm.
  • Know how to structure the algorithm in code, which often looks different than it does in a paper.
  • Implement the technical nuances of the algorithm (e.g. sending packets, etc).
  • Learn Rust semantics and how to work with the language (e.g. Borrow Checker, type-system, etc).

Each of these problems are individually challenging. Solving them all together is a recipe for failure, collectively representing so much uncertainty that–if you don’t break it into pieces and solve them independently–you may be unable to progress at all.

If the goal is actually to learn Rust, we’ll do much better to try implementing a simple program first to learn the operational semantics of the language. Then a program with more structure, helping learn types and modules. Finally, once you’ve really studied the paper, you might try implementing Raft.

A few situations and how this rule applies:

  • Never written Rust before? Start simple first, write a basic program with no structure.
  • Understand Rust but haven’t built a networked app yet? Practice networking in a toy program first.
  • Already implemented Raft in another language? Great, you can probably get going as the only new thing you’re learning is the language.

The last point is interesting: this rule isn’t always about limiting your ambition, it’s about helping you identify if you should break down a problem, or solve it wholesale. It’s about ensuring you can make consistent, incremental progress more than anything else, as–over time–the only thing that matters for solving hard problems is whether you can sustain your energy to persist.

Focusing your learning

You might be working to build a thing, or you might be trying to learn something.

Doing both at once is suboptimal, though often unavoidable when your goal is to ship a thing. If your goal is learning, though, you can likely focus exclusively on that and be more effective as a result.

I found learning how to write Go code difficult. A beginner level of proficiency came quickly but it took me a long time to learn the idioms and find patterns that weren’t a rephrasing from another language that didn’t quite fit.

To break out of this, I took a codebase I was familiar with and rewrote it many times over, incrementally improving the quality of the code. I’d spent so much time working on it that I knew the problem space really well, letting me focus entirely on the code itself.

Conveniently, the codebase is open-source: gocardless/pgsql-cluster-manager, an HA Postgres cluster supervisor that manages a proxy to route incoming Postgres connections to whichever Postgres node is the current primary.

This project was the first Go codebase I’d ever built myself. The original code was functional but messy, leading me to rewrite it almost from scratch in an attempt to fix some of my annoyances with the initial version. When we deprecated Corosync and Pacemaker (which pgsql relied on) we rewrote the project into gocardless/stolon-pgbouncer, now the third time I’d tried solving this problem, cementiong the lessons from previous attempts.

Eventually, I took everything I’d learned and applied it building pgsink, a Postgres change-capture tool that I used as a sandbox to practice improving my Go code for several years.

Rehashing the same problem gave me the headspace to really learn. There’s no way I’d have improved as much over three different projects.

Applied to teams

If you lead technical teams, you should be incorporating this rule on the daily.

When onboarding a new joiner or helping develop existing members, you want to help people pick projects that meet this criteria.

The challenges found in a team context are usually:

  • Product, where you need to know an existing product well in order to deliver.
  • Business, when the work requires navigating the organisation, e.g. project with a lot of stakeholders.
  • Technical, if the work requires technical proficiency in a tool or system, e.g. building a feature that needs to be scalable.

Ideal staffing configurations are:

  • Everyone is comfortable across all areas ⇒ unlikely, and often neglecting an opportunity to develop someone.
  • One person on the team is new to one of the challenges ⇒ ideal sweet spot, where the team can invest time supporting that person and upskilling them while making good progress.
  • No mutual gaps ⇒ collectively, the team have expertise and experience in all the challenges this project presents, even if some are new to some on the team.

Not always possible, but these are the conditions where individual growth can be afforded without it impacting delivery. If you can arrange projects to adhere to this, you should be able to grow your team sustainably while allowing you to ship consistently.

If you liked this post and want to see more, follow me at @lawrjones.