Have you ever failed to learn a new programming language in your spare time? You pick a small project to implement, get a few functions written… and then you run out of time and motivation. So you give up, at least until the next time you give it a try.

There’s a better way to learn new programming languages, a method that I’ve applied multiple times. Where starting a side project often ends in failure and little knowledge gained, this method starts with success. For example, the last time I did this was with Ruby: I started by publishing a whole new Ruby Gem, and getting a bug fix accepted into the Sinatra framework.

In this post I will:

Explain why a side project is a difficult way to learn a new language.

Share my story of learning a tiny bit of Ruby.

Explain my preferred learning method in detail.

Side projects: the hard way to learn a language

Creating a new software project from scratch in your spare time is a tempting way to learn a new language. You get to build something new: building stuff is fun. You get to pick your language: you have the freedom to choose.

Unfortunately, learning via a side project is a difficult way to learn a new language. When you’re learning a new programming language you need to learn:

The build and run toolchain.

The packaging toolchain.

The testing toolchain.

How to use 3rd party packages.

The syntax.

The semantics: memory management, units of abstraction, concurrency, execution model, and so forth.

Standard idioms.

The standard library.

Common 3rd party libraries relevant to your problem domain: which to use, and their APIs.

This is a huge amount of knowledge, and you’re doing so with multiple handicaps:

Learning on your own: You have to figure out everything on your own.

Blank slate: You’re starting from scratch. Quite often there’s no scaffolding to help you, no good starting point to get you going.

Simultaneous learning: You are trying to learn everything in the list above at the same time.

Limited time: You’re doing this in your spare time, so you may have only limited amounts of spare time to apply to the task.

Lack of motivation: If you care about the side project’s success, you probably will be motivated to switch back to a language you know. If you just care about learning the language, you’ll be less motivated to do all the boring work to make the side project succeed.

Vague goals: “Learning a language” is an open-ended task, since there’s always more to learn. How will you know you’ve achieved something?

Personally I have very limited free time: I can’t start a new side project in a language I already know, let alone a new one. But I do occasionally learn a new language.

That time I learned some Ruby

Rather than learning new languages at home, I use a better method: learning a language by solving problems at my job.

For example, I know very little Ruby, and when I started learning it I knew even less. One day, however, I joined a company that was publishing a SDK in multiple languages, one of which was Ruby.

A tiny gem

My first task involving Ruby was integrating the SDK with popular Ruby HTTP clients and servers. Which is to say, I started learning a new language with a specific goal, motivation, and time to learn at work. Much better than a personal side project!

I started by learning one thing, not multiple things simultaneously: which 3rd party HTTP libraries were popular. Once I’d found the popular HTTP clients and servers, my next task was implementing the SDK integration. One integration was with Sinatra, a popular Ruby HTTP server framework.

As a coding task this was pretty simple:

The Sinatra docs pointed me towards a library called Rack, a standard way to write HTTP server middleware for Ruby. Rack has documentation and tutorials on how to create middleware. There are lots of pre-existing middleware packages I could use as examples, for both the code itself and for tests. I only needed to learn just enough Ruby syntax and semantics to write the middleware. Googling tutorials was enough for that.

I learned just enough to implement the middleware: 40 lines of trivial code.

Next I needed to package the middleware as a gem, Ruby’s packaging format. Once again, I was only working on a single task, a well-documented task with many examples. And I had motivation, specific goals, examples to build off of, and the time to do it.

At this point I’d learned: a tiny bit of syntax and semantics, some 3rd party libraries, packaging, and a little bit of the toolchain.

A bugfix to an existing project

Shortly after creating our SDK integration I discovered a bug in Sinatra: Sinatra middleware was only initialized after the first request. So I tracked down the bug in Sinatra… which gave me an opportunity to learn more of the language’s syntax, semantics, and idioms by reading a real-world existing code base. And, of course, the all-important skill of knowing how to add debug print statements to the code.

Reading code is a lot easier than writing code. And since Sinatra was a pre-existing code base, I could rely on pre-existing tests as examples when I wrote a test for my patch. I didn’t need to figure out how to structure a large project, or every edge case of the syntax that wasn’t relevant to the bug. I had a specific goal, and I learned just enough to reach it.

At the end of the process above I still couldn’t start a Ruby project from scratch, or write more than tiny amounts of Ruby. And I haven’t done much with it since. But I do know enough to deal with packaging, and if I ever started writing Ruby again I’d start with a lot more knowledge of the toolchain, to the point where I’d be able to focus purely on syntax and semantics.

But I’ve used a similar method to learn other languages to a much greater extent: I learned C++ by joining a company that used it, and I became a pretty fluent C++ programmer for a while.

Learning a new language: a better method

How should you learn a new programming language? As in my story above, the best way to do so is at work, and ideally by joining an existing project.

Existing projects

The easiest way to learn a new language is to join an existing project or company that uses a language you don’t know. None of the problems you would have with a side project apply:

There’s lot of existing examples to learn from and modify, you’re not starting with blank slate.

You don’t have to learn the build/run, testing, and packaging toolchains before you can do anything useful: it’s mostly going to be setup for you, so you can learn it by osmosis over time.

You have specific goals: fix this bug, add this feature.

You have co-workers you can ask for help, who can review your code, and can help you write more idiomatically.

New projects

Lacking an existing project to join, look out for opportunities where there’s a strong motivation for your project to add a new language. Some examples:

You need to do some data science, so you need to add Python or maybe R.

Your project has a problem with a computationally intensive bottleneck, so you need to add something like Rust, C++, or C.

Starting a new project is not quite as easy a learning experience, unfortunately. But you’re still starting with specific goals in mind, and with time at work to learn the language.

Make sure to limit yourself to only learning one thing at a time. In my example above I sequentially learned about: which 3rd party libraries existed, the API for one library, writing miniscule amounts of trivial integration code, packaging, and then how to read a lot more syntax and semantics. If you’re doing this with co-workers you can split up tasks: you do the packaging while your co-worker builds the first prototype, and then you can teach each other what you’ve learned.

Learning at work is the best learning

More broadly, your job is a wonderful place to learn. Every task you do at work involves skills, skills you can practice and improve. You can get better at debugging, or notice a repetitive task and automate it, or learn how to write better bug reports. Perhaps you could figure out what needs changing so you can get make changes done faster (processes? architecture? APIs?). Maybe you can figure out how to test your code better to reduce the number of bugs you ship. And if that’s not enough, Julia Evans has even more ideas.

In all these cases you’ll have motivation, specific goals, time, and often an existing code base to build off of. And best of all, you’ll be able to learn while you’re getting paid.