Some of the most important lessons I learned in college came from one professor, Michael Mitzenmacher. Now, this was a guy with a lot of papers to his name, tenured at Harvard, working on some pretty darn complicated computer science theory (I took his algorithms class). So you'd expect that I'd learn something important. But as it turned out, the biggest lessons I learned from him weren't on the topics he taught. I learned a secret that helped me learn much more effectively.
At one point when describing the problem sets in the class, he gave some advice that stuck with me:
Don't suffer from Math Major syndrome
So, what you ask, is Math Major syndrome? Well, think of it this way--if you are studying a hard problem, what's your first tendency? To get lost in thought? To start at it, mulling over ideas in your head, waiting for a flash of insight? If so, that's math major syndrome. It's the idea that you can (or have to) solve problems entirely through a brilliant flash of insight, without doing any actual work.
The alternative is quite simple:
Hard problems become easier by working through them with diagrams, effort and patience
In other words, don't just wait for answers to come to you--go out and find them. Don't just use what's in your head--use paper, or the computer, or a whiteboard, to draw out the ideas, try experiments, make the patterns visible rather than waiting for a flash of insight.
I remember really learning this lesson during the first problem set--I'd started it when it was handed out, but the night before it was due I still had one problem left. I spent hours on that problem, but I spent it drawing out equations, working through possiblities. Each one ended in failure, until I had a flash of insight in the middle of writing out an equation. If I hadn't worked through (and discarded) all those possibilities, I'd have had no hope of solving that problem.
So how do you apply this principle in practice?
Program Program Program
If you're learning to program, you should be programming. Work practice problems while you read tutorials--in fact, you're probably better off in most cases working practice problems than you are reading about programming. (You will need to read, but without practice, that reading means nothing.)
In fact, if you're just learning to program, then I suggest that you stop reading this article right now and go write some code (or get yourself set up with a compiler and then go write some code). But not something you know how to solve--try to write a program that is just a little bit scary or hard. You should feel just a bit scared that you won't be able to do it.
Draw it Out
A second corollary to the principle is that you're better off using paper than trying to imagine everything. In fact, I can't say enough about the importance of drawing diagrams. A lot of things in programming sound very abstract or difficult to describe in words, but they can be quickly shown with a picture. Computer memory, for example, can be thought of as a long block of cells (almost like in an Excel spreadsheet). Drawing out each piece of memory in your program (when dealing with pointers or data structures) can really help visualize what is going on. You can also write out recursion manually by showing each recursive call with its arguments and return value.
But even simpler concepts become clear when you draw them or work through them step by step--having trouble understanding a complex piece of code? Write out the different variables and how they are modified. For example, the boolean expression
! a || b
(in other words, NOT a OR b)
has a very specific meaning in English: "if a is true, then b must be true"; this could be useful for checking user inputs ("if the user pressed exit, then the game must be over--otherwise, print out an error"). But it's not at all obvious what ! a || b means unless you actually work through it. What do I mean?
The statement "if a is true, then b must be true" requires that:
if a = true, b must be true
if a = false, b can be either true or false
How does that work for the expression "! a || b? You can draw out the table by writing a in one column, b in a second, and whether the condition is satisfied result in a third:
a b ! a || b
0 0 1
0 1 1
1 1 1
1 0 0
Now you can more quickly see that the table expresses the English condition. The condition is satisfied in all the cases except when a is true and b is false (clearly, that can't satisfy an expression that says, "if as is true, then b is true"!)
Being able to look at the data really helps!