Deliberate Practice for Software Developers

the practice of programming

In Making Sense of the Deliberate Practice Debate and Coding is Underrated, I introduced the concept of deliberate practice, and suggested a specific skill that aspiring software development experts can use as their practice target. I’m now going to go into detail on a deliberate practice process for this skill. Here’s the skill description again:

Write correct, efficient, and maintainable code for a software component given well-defined requirements.

Remember that we’re not just hoping that the requirements will be well-defined. At the instant that you sit down in front of your editor to implement a section of code, your requirements have to be well-defined. If they’re not, then the code that you write becomes the requirement, possibly the wrong one. If you’re not sure about a requirement, clear up any questions before you start coding. Defining requirements is another skill with its own deliberate practice process. For the purpose of the process explained below, I’m going to assume that an expert in that skill (maybe you) has clearly defined the requirements.

Deliberate Practice: A framework for learning complex skills

Prior to 1993, writers used the expression “deliberate practice” for its literal meaning: actions carried out in a conscious and intentional manner. For example, consider this description of William Blake‘s work habits, from Ye Olde Popular Science Magazine, 1875 edition:

Blake was a painter by day and a poet by night; he often got out of bed at midnight and wrote for hours, following by instinct the deliberate practice of less impulsive workers.

Since the publication in 1993 of an academic paper entitled “The Role of Deliberate Practice in the Acquisition of Expert Performance” by K. Anders Ericsson and colleagues, the term “deliberate practice” took on a more specific meaning in the psychology literature, and eventually in popular books and articles as well. Here’s how the authors of the paper define deliberate practice in terms of other activities:

Consider three general types of activities, namely, work, play, and deliberate practice. Work includes public performance, competitions, services rendered for pay, and other activities directly motivated by external rewards. Play includes activities that have no explicit goal and that are inherently enjoyable. Deliberate practice includes activities that have been specially designed to improve the current level of performance.

“Activities that have been specially designed to improve the current level of performance” is a good summary definition of deliberate practice. But the Ericsson paper and other research published since then contains much more detail on the nature of those specially designed activities. A useful reference is Geoff Colvin’s book Talent is Overrated. In Chapter 5, “What Deliberate Practice Is and Isn’t,” Colvin describes a set of five “elements” that characterize deliberate practice. Here are two online sources where you can find a description of these elements:

Deliberate Practice for Software Developers

If deliberate practice is the way to become an expert, then a good first step on that journey is to take the elements of deliberate practice and customize them for a specific target skill. That’s what I’m going to do for the software development skill that I described. I’ll refer to that skill as coding mastery. For each of the five deliberate practice elements, I’ll include a summary of how Colvin defines the element, and then my interpretation of how the element applies to the coding mastery skill.

Deliberate Practice Element #1: “It’s designed specifically to improve performance”

Summary: The key word here is designed. The best person to design a practice routine for a student is almost always a teacher or coach. Experienced students may be able to design their own routine, but only if they are unusually perceptive about their own performance. Even elite performers like professional athletes rely on coaches to design their practice. To design a practice routine, the student or coach must select a skill that needs improvement, and then find an activity that exercises that skill at a level that is slightly higher than the student’s current ability. It helps to define the skill clearly before designing an activity to improve it. With enough repetitions, the student’s ability improves until the routine is no longer difficult enough to induce any improvement in performance. At that point, the student has to move on to a more difficult activity, or a new skill.

Application to coding mastery: If you’re designing a coding mastery practice routine and you’re in college studying Computer Science or a related field, you’re in luck. That’s the best place to find someone who can help. If you’re working at a software company, you may also be able to find a mentor to provide advice on getting better. If you don’t have an expert coach, here are some ideas on designing a practice routine.

There’s no way to get better at coding without writing code. However, the premise of deliberate practice theory is that not all types of practice are equally effective. Most developers get the majority of their coding practice on the job, by working on a project. This could be a project for work (for an employer or a client). It could be a side project, in an area of personal interest. Or it could be a school project, for those who are just starting out. The ultimate goal of programming is to write software that people use. Working on the end-to-end requirements of a full software project can provide valuable real-world experience that can’t be obtained any other way. However, using projects as your only coding practice presents some difficulties. Your main goal with this type of work is to finish the project for your client or professor. While you’re focusing on this goal, it can be difficult to also focus on the goal of becoming a better programmer.

To overcome the limitations of project work practice, I would recommend that you add some time to your daily schedule for a different type of practice. Here’s the practice routine that I have in mind: First, select a source of programming problems that you can use for practice. The problems should have solutions available, so you can check your work, and you should have some way to evaluate the difficulty of a problem, so you can select a problem that’s slightly more difficult than you are comfortable with. It’s also useful to have a template file that contains common code that you don’t want to re-write every time you start a new problem — for example, code to read from a text file. While it may be instructive to write this template code from scratch a few times, you’ll soon reach the point where repeatedly writing it won’t lead to any learning benefit.

Once you have those prerequisites taken care of, carry out the following process for one problem at a time:

  • Start a timer.
  • Select a problem.
  • Make a fresh copy of your template code.
  • Read through the problem statement, making notes about requirements and potential difficulties. Re-read the problem statement if necessary until you understand the requirements, or at least until you can make an assumption for each unclear requirement.
  • Solve the problem on paper
  • Expand the solution into pseudocode.
  • Translate the pseudocode into real code, making note of any parts that you find difficult to translate.
  • Test locally and debug if necessary until you’re satisfied with your answer.
  • Check your answer by submitting your solution or, if you’re using an offline source of problems, looking up the answer. Debug and recheck as necessary until you have a correct solution.
  • Stop the timer and record the elapsed time.

After you finish each problem, ask yourself if you can improve any aspect of your problem-solving process based on your experience with that problem. Some potential improvements:

  • Refactor your template to include additional code that you find yourself writing frequently.
  • Revise the process steps above based on your experience using them, and your personal style.
  • Plan a deliberate practice session for the areas of difficulty that you noted as you were solving the problem. For example, you may have had trouble with particular language syntax. To get more comfortable with that syntax, repeatedly practice it in isolation until you can use it without thinking about it.
  • Review other implementations of the same problem (e.g., that people have made available online), and see if there are any ideas you can learn from.

When I posted this process on Quora for review, I got some criticism of the bolded step, “Solve the problem on paper.” Miguel Oliveira pointed out that if a student doesn’t know how to solve a problem, this process doesn’t help them much. That is a true statement. This process is different from a mathematical problem-solving process such as the one described in George Pólya’s famous manual, How to Solve It. Rather than focusing on one problem, this process is designed to guide a student through a set of programming problems in such a way that experience gained on each problem can be applied to the next one. Therefore, it works best on a set of problems of increasing difficulty. Also, because it is targeted at a programming skill, there is necessarily an emphasis on implementation. This makes it different from a process for solving mathematical problems, where the “solve the problem on paper” step would complete the process. It’s also worth pointing out that, when successful competitive programmers describe their training strategy, they emphasize solving a large number of problems of increasing difficulty as a way to gain expertise. This suggests that they developed their problem-solving skills mainly through experience solving competitive programming problems. This process is designed to encourage that same approach.

While I am recommending the process shown above as a deliberate practice approach to coding mastery, mathematical problem solving is a relevant skill for software developers, and one which could be effectively targeted by deliberate practice techniques. For programmers who are interested in developing math expertise, a separate deliberate practice effort could be designed using math problems (e.g., from a discrete math textbook) and Pólya’s problem-solving process. This could even help with competitive programming. Some high school and college competitive programmers in team-based competitions designate a math expert on the team who works out the math parts of the problems while other team members focus on pseudocode and implementation.

Deliberate Practice Element #2: “It can be repeated a lot”

Summary: In work or play situations, you may need to use a particular skill only rarely, but when you do need that skill, it’s important to get it right. Canonical examples include a tricky section of music that appears once in a performance, and the golf shot that comes up once in a tournament. (Colvin reports that Tiger Woods “has been seen to drop golf balls into a sand trap and step on them, then practice shots from that near-impossible lie.”) You should design your deliberate practice routine so that you can repeat skills as often as required to master them. The number of repetitions that elite practitioners use can be quite astounding, as illustrated in a coach’s story about Kobe Bryant’s early-morning practice.

Application to coding mastery: Mastering programming, like mastering any complex skill, requires a lot of repetition. One goal of the process described in Element #1 is to uncover skills that need work. Repetition can then be used to master these skills. As you work on programming problems, make a note of anything that is at all difficult. For example, if you have to look up the syntax of a framework method, or even if you have to think about it for longer than it takes to type it, make a note of that. When you’re done with the problem, you can construct your own mini-problems to work on these areas. Having every aspect of your chosen programming language at your fingertips will free up your brain to work on more difficult matters. Like Kobe with his jump shots, you can repeat these mini-problems until you no longer have to think about the skill you’re practicing. Then when the skill comes up in a future problem, or in code that you’re writing at work, you’ll be ready to use it.

Deliberate Practice Element #3: “Feedback on results is continuously available”

Summary: After each practice repetition, the student needs to evaluate their performance against an objective standard, and consider how they can improve the next repetition. In the chess world, one way that serious students get feedback is to go through a grandmaster-level game, and consider at each step what move they would make in the same situation. Then they get feedback by looking at the actual move that the more experienced player made.

Application to coding mastery: Programming is an activity that inherently provides a lot of feedback. You tell the computer what you want it to do, in a way that you think it will understand, and you get feedback by compiling and running your problem. A good source of programming problems can provide even more feedback. Here is the type of feedback you should look for as you’re following the process explained in Element #1:

  • By keeping track of the amount of time it takes you to solve problems, you get feedback about whether your practice is making you faster at solving problems of similar difficulty, or solving problems a second time that you have already solved once.
  • Problem statements often include a mixture of important details and extraneous information. By making notes as you first read through a problem statement, and going over your notes after you solve the problem, you can keep track of your ability to separate critical information from filler.
  • By solving the problem “on paper,” you can separate your problem-solving skills from your software implementation skills, and work on improving them separately. You don’t have to literally use a pencil on a dead tree. You just have to think about the solution before you worry about how you will describe it in a structured form, or to a compiler.
  • By writing pseudocode before you write real code, you can separate issues of programming language syntax from your ability to express your solution in a structured manner.
  • Each time you write code and compile your program, you’ll get feedback from the compiler. Errors and warnings should go into your notes, so that you can brainstorm ways to avoid them.
  • When you test your implementation, test results are a source of feedback. Why did a test fail? How good are you at thinking of all the edge cases?
  • If you’re using an online judge, it’s an important source of feedback. Each time you submit and don’t get an “accepted” result, you should make a note to think of ways to get your submission right the first time.
  • Once you have a successful submission, or if you feel you have spent sufficient time without finding a solution, you should look for other people’s implementations. This is one of the best sources of feedback, since it can show you techniques that you may not have come up with on your own.

Deliberate Practice Element #4: “It’s highly demanding mentally”

Summary: As described in elements #1-#3, the deliberate practice process depends on working slightly above your current skill level, working with a practice routine over many repetitions, and constantly observing your own performance to find and correct flaws. This process requires focus, concentration, and a strong determination to get better. Consequently, it isn’t necessary or realistic to spend a long time every day in deliberate practice. Even elite performers max out at 4-5 hours per day.

Application to coding mastery: Because deliberate programming practice is different from regular programming, it’s best to separate it from any programming work that you’re already doing. I recommend treating it as a task on your daily list of things to do. The Pomodoro Technique is a popular productivity technique for tasks that require focus. You could start by doing one Pomodoro (25 minutes) per day on deliberate programming practice, and increase that number as you get more practice. The key is to have a focused mindset during your practice time, and not try to multitask.

Deliberate Practice Element #5: “It isn’t much fun”

Summary: Using your skills at your current level of proficiency is more fun than pushing to a new level. So most people will find a plateau and stay there. If you use deliberate practice techniques, you’ll be able to surpass them in your chosen skill.

Application to coding mastery: This element can be tricky. It doesn’t mean that deliberate practice is never rewarding. If you can’t stand the process of solving programming puzzles, or don’t like studying algorithms, it’s unlikely that you’ll stick with competitive programming problems for very long. But deliberate practice should feel different from work, play, or regular practice. If your deliberate practice feels easy, then you’re not working on something that is above your current skill level, which means you’re not getting everything you can out of the process.

The Benefit of Deliberate Practice for Developers

As Colvin and Newport point out, the habit of deliberate practice is much more entrenched in sports and music than it is in knowledge work. Although deliberate practice has become a well-known concept for people who follow productivity and learning trends, it’s still not part of mainstream software development culture. This is both good news and bad news. The bad news is that unless you’re in college, it can be hard to find a coach or mentor who has thought a lot about creating a software development practice system. The good news is that if you know something about deliberate practice and you make a habit of using it, it will give you a substantial competitive advantage.

(Image credit: Alexandre Dulaunoy)