Emö Rubik invented the Rubik’s Cube in 1974 and it became the world’s most popular puzzle. The cube consists of 26 cubelets that move and turn when the faces are twisted. This cube (pictured above) is in a solved position when each face is a uniform color. The goal is to take a randomized cube though a series of face twists to transform it into the solved position. Learning to solve a Rubik’s Cube can teach us something about learning to program.
Programming has always been seen as a skill in addition to a thinking process. But what exactly does it mean when we say programming is a skill? How is this a useful insight?
Here is the usual story about skills. A skill is an ability developed over time with practice that became embodied and automatic. We do not need to think about a skill; we just do it. We can perform at different levels of competence including beginner, advanced beginner, competent, proficient, expert, and master. This idea of levels of competence can be seen in many places—for example the five levels of airline pilot certification, or the achievement levels in many online games. It takes time, practice, commitment, and experience to progress through the levels: from hesitant, mistake-prone, rule-based behaviors as a beginner to fully embodied, intuitive, and game-changing behaviors as a master. Most skills are supported by sensibilities, or being able to sense moods, emotions, and how others will react. A skill is both individual and social—standards for levels of competence are set by a community. A skill is not the application of knowledge; you cannot become good at something simply by reading books, watching videos, or listening to lectures. You need practice until you are good at it. When you embody a skill, you perform it without having to think much about it; your body automatically behaves in a skillful way.
Unfortunately, this story does not help the many novice programmers who are among my students. They already know they must practice a lot, but they get frustrated by the number of mistakes they make and discouraged by the time it takes to find and correct mistakes. They know expert programmers can help them, but are often put off when the experts tell them what to do rather than let them discover for themselves. In short, the usual skill story may be correct, but it does not help with basic concerns among beginning programmers.
Let me tell this story from another angle and see if we can learn anything about learning to program well.
Recently I discovered a decaying business card in a hidden compartment of my wallet. It was a crib sheet I wrote in 1983 to help me remember how to solve the Rubik’s Cube, which was very popular at the time. I put down the Cube for the last time in 1984 after mastering it because it was no longer a challenge. I never thought about it again. But the crib sheet rekindled my interest. I found my old Cube and stared at it and the crib sheet, only to discover that I remembered absolutely nothing at all about the strategy of solving the Cube or what the individual formulas on my crib sheet meant. To resurrect my defunct skill I decided I needed to relearn the solution method from scratch.
To get started, I searched the Web for solution methods. I found many. Some are easy to remember; others are complex and used only by speed experts. In 1983 there was no Web to search and no books to read. It was very hard to find anyone who had a method, and I did not have the patience to work out my own method. Given the Cube’s novelty, most of the 1983 solvers were mathematicians who used group theory to find solutions. Each configuration of the cube was an element of the group, and each twist operation permuted some of the cubelets. The objective was to find algorithms—each a series of twists of the faces—that had a net effect of repositioning two or three cubelets and leaving everything else untouched. These algorithms could then be used as tools to work a random Cube into a solved Cube.
Over the years, the cubing community evolved many Cube-solving strategies. They did this by trial and error, not by applying group theory. The simplest strategy is to solve the bottom layer, then the middle, and finally the top. Each layer gets a little harder because the algorithms to solve the middle layer cannot affect anything in the bottom layer, and similarly the algorithms to solve the top cannot affect anything in the other two.
In visiting various web pages, I learned cube solvers had developed a standard notation for twists. They had also developed libraries of standard algorithms. The notation and algorithms were not settled in 1983. I learned most of the easier solution strategies relied on about eight algorithms and required about 180 twists to solve a cube, but advanced cubers have evolved larger libraries of algorithms with which they solve cubes in about 60 twists. Also, it always takes at least 20 twists to solve a cube.
There are even speed tournaments for cube solvers. Contestants get a randomized cube and then they solve it as fast as possible while judges measure them with stopwatches. I found demonstrations on YouTube of some advanced cubers doing their work. All I could see is a flurry of fingertip motions over in a jiffy. The world record is just over 5 seconds. Can you believe that? Theoretically an optimal solver has to complete 20 twists in 5 seconds to beat that record. An expert solver probably completes up to 8 twists a second, and can finish fast because of a large library of specialized but efficient algorithms. It’s hard to believe anyone can move that fast, but when you see the videos you know it’s true.
I also discovered some expert cubers had designed their own cubes that moved exceptionally slickly and did not “pop.” If you were too clumsy with an older Cube you could suddenly wedge a face in mid twist and the whole thing pops apart. I went and bought a “tournament Cube” and it was indeed slick and smooth compared with my original Cube.
That is what I learned from my research. Now I had to put it to work with practice.
First I had to select one of many solution strategies and learn its algorithms. This was not as straightforward as I imagined. I chose an eight-algorithm strategy described by Arthur Benjamin, a mathematics professor from Johns Hopkins, who learned it from a world champion cuber. At first I kept trying to relate his eight algorithms to the mysterious notations on my wallet crib sheet. No luck. Waste of time. Finally I decided to pretend I knew nothing, learn the algorithms, and see where I wound up. My first cube solution took me about an hour. I kept making mistakes and having to start over almost at the beginning. It was frustrating and exhausting. When I got my first solution, I was surprised that it worked at all.
The next day I tried again. I achieved my first solution in about 45 minutes (not as many mistakes) and a second solution in 30 minutes. The overall layer-by-layer strategy started making more sense and I made fewer mistakes.
I practiced the algorithms for about an hour a day. It paid off. On the fifth day I was able to solve a Cube in five minutes. I wrote down the key algorithms I needed on a new crib file card. As long as I could remember when each algorithm should be applied I could solve the cube in five minutes. Occasionally I made mistakes and had to start over.
On the sixth day I decided to memorize the algorithms. I gave them funny mnemonics such as “the rural algorithm,” “the furl-unfurl algorithm,” “the rouser algorithm,” the “radio frequency algorithm,” and the “fast-fourier algorithm.” (These odd names are plays on the letters F, U, R, L, and B that name all the twists in the algorithms I was using.) Each algorithm had progressively more twists, but with some chunking I was able to correctly remember each one. For example, the rouser algorithm takes eight twists, the radio frequency algorithm 12 twists, and the fast-fourier algorithm 12 twists. At first I had to close my eyes and concentrate so I did not make any mistakes, but soon I developed a kinesthetic sense of how each algorithm “felt.” I could instantly detect if I made a mistake because it felt wrong allowing me to reverse the errant twist. After a short time of practice I no longer had to whisper the steps to myself, as each algorithmic sequence just seemed to flow out.
On the seventh day I tried these algorithms on solved Cubes to see how cubelets were being permuted. I realized that if most of the algorithms are repeated four to six times the cube is restored to its solved position. I was then able to easily figure out how to orient the cube before applying an algorithm, thus shortening my solution time to three minutes.
This was an amazing progression, from a rank beginner to a pretty competent cuber in seven days.
I learned a number of lessons from this—all of which can be applied to becoming and staying competent as a programmer.
I experienced moving from a beginner to advanced beginner to competent. Each stage had its own feel. In the beginner stage, I knew nothing and was easily frustrated that I knew nothing. I kept making mistakes that were very costly; the time it took to recover was significant. My frustration was compounded because I’m an expert in other fields and I am used to being an expert. It had been so long since I was a beginner I had no memory of being a beginner. Even though I had no choice but to be a beginner, I fought it mightily. I only made progress when I finally decided to accept that I was a beginner and knew nothing.
By the third day I had developed a bit of a “feel” for some of the moves and configurations. They looked and felt familiar. It took me less time and I made fewer mistakes. My mood changed from beginner’s frustration to a sense of hope that I could learn this; I had a sense of ambition that I could become competent.
By the sixth day I had a strong “feel” for the moves and could quickly tell by inspection how to orient the cube so that the algorithm required the fewest repetitions. I was developing a mood of confidence that I knew what I was doing.
By the seventh day I paid attention to details I would never have noticed on day one, such as watching how the two advanced algorithms permuted the cubelets. My confidence had grown and I felt I could do well in a tournament of people who could solve in three minutes.
The entire process, from day one to day seven, was also an experience of expanding awareness of the cube and all the social practices (such as community-generated algorithms and tournaments) that had grown up around it.
Seven days of about an hour practice a day moved me from a rank beginner to a competent cuber. I was not proficient or tournament ready, but I could solve cubes every time without making mistakes. I experienced the growing sense of confidence in my performance ability and the falling away of the frustrations of “beginnerhood.” I had moved from an intellectual experience as a beginner (what rule do I apply next?), to a kinesthetic experience (what does the right algorithm feel like?). I developed a new appreciation for the travails experienced by a beginner and the importance of the wisdom to keep practicing even if you don’t see yourself making progress.
In other words, in seven days I experienced in a microcosm the same thing I had experienced in slow motion when I learned programming. I now have a much greater appreciation of how a programmer chooses a good algorithm for each step of a design. When I was a beginner, this choosing was purely an intellectual exercise and as I became competent it transformed to a kinesthetic exercise. When we say the good programmers have a good “feel” for designing programs, I see that is literally true.
Although I once had a cuber skill, this whole experience did not feel like relearning a long-lost skill. It felt like learning a new skill from scratch. What I had really lost, and have now recovered, was a sense of how to be a beginner.
So much programming today is done by reuse—snipping code from other sources or grabbing code segments from libraries. That is just like selecting an algorithm to move the cube one step closer to solution. When you get good at that kind of programming, you have a kinesthetic feel for what code segments will work best. Of course programming is not completely kinesthetic, because you have to think about what you are doing.
Try this yourself. Get a Rubik’s Cube, find a solution strategy, and do this experiment. See what it is like to be a beginner. See how unhelpful it is for an expert to tell you as the beginner how to do things. The best thing an expert can do is guide your beginner practice to find those aha learning moments for yourself. Without the practice and learning moments you will never progress beyond beginner. I speculate the experiment will give you compassion for beginners and restore your memory of what beginnerhood was like. I gained a lot of compassion for myself; at the times when I thrust into having to be a beginner my tendency was fight mightily against being a beginner. I speculate it will give you compassion for yourself too: The next time you find yourself having to be a beginner, you will know what to do.