On doing well in CS51 #
This course is intended to impart two quite different types of knowledge. Firstly, we present a set of concepts that are of general use in thinking computationally and in engaging in the act of programming. These include concepts like first-order and higher-order functions, recursive definition, algorithmic analysis and big-O notation, and the like. Second, we provide a set of exercises through labs and problems sets to provide practice in a set of skills, namely programming skills in a variety of paradigms. To do well in the course, you’ll want to organize your efforts to best learn these two quite different kinds of knowledge.
The latter – skill-based – knowledge in particular requires a specific approach. Remember when you learned to ride a bicycle? (If you never learned to ride a bicycle, think about kicking a soccer ball or swimming or playing piano or whatever skill you pride yourself on.) The first few times you tried, you probably fell off the bike, scraped your knees, got frustrated. It took time to acquire this skill. You can’t learn how to ride a bike by reading a book about bike-riding or watching a Youtube video; you have to put in the time with your butt on the seat. Programming is like that – without the scraped knees, but definitely with the frustration. It requires a certain amount of time to “get it”. How long varies, and you’ll have to come to terms with the fact that there aren’t any magic shortcuts to continued practice. But there are some things you can do to help the process along, which we’ll discuss below in the context of the course’s parts: labs, problem sets, and exams.
A digressive return to bike-riding: Riding a bike requires you to simultaneously perform two skills, peddling for locomotion and balancing for staying upright. The traditional approach to learning how to ride a bike is to start with riding a four-wheeled vehicle, a bike with training wheels, so that you can concentrate on the pedaling without worrying so much about the balancing. Once pedaling is mastered, the training wheels can be removed as you work on learning how to balance. At least that’s the theory.
In the early 2000s, however, a different approach to learning how to ride gained traction. In this approach, the rider starts with learning to balance first, moving on to pedaling only after balancing is mastered. Using a balance bike (see the figure above), which has no pedals and a low seat that allows the rider to push the bike along, balancing is the only skill required. It turns out that this is a much better method for learning to ride a bike. By ordering the skills properly, and not moving on from one skill to the next until the student is ready, skill acquisition can be done faster and more reliably.
It works that way with the programming skills taught in CS51 as well. We’ve carefully ordered the skills required in CS51 so that the earlier ones prepare you for the later ones. The labs are intended to provide exactly this graduated and well-ordered set of exercises. But you’ll still need to put the time in to master the skills before moving on.
Labs emphasize your making a good faith effort, rather than how well your solutions work, because we want to encourage this freedom of exploration – and the frustration that accompanies it – during skill development. The goal of the labs is not to “get the right answer” for its own sake. Rather, the goal is to gain understanding of the concepts and the skill to deploy them. Until you’ve done that, you haven’t finished the lab. With that in mind, here’s my recommendations for getting the most out of the labs.
Do the readings before your lab session. As you do the readings, think about what concepts you’re finding most confusing, not only so you’ll be able to discuss them in the post-lab survey, but because it will prime your attention during the lab.
The readings include exercises. Stopping to do the exercises can be very helpful; that’s why they’re there. Solutions to many of the exercises can be found in the back of the book, often with additional expanatory material, most importantly explanations of alternatives. But don’t just skip directly to the solution. Try to do the exercise first.
Labs use a system called pair programming, in which two programmers collaborate using a single computer. One person “drives” (typing code at the keyboard) while the other “navigates” (reviewing the typed code to look out for errors or alternatives), with constant communication between the two. Both participants should be fully engaged at all times. You should switch roles frequently, at least at every exercise.
In lab, work with your lab partner to do a thorough job on as much of the lab as you can. Talk the problems over. Make sure you both understand not only what the solution is, but why, and why there aren’t better ways of doing it. It’s better to really understand a smaller number of lab exercises well than to “get through” a larger number during the session.
Taking full advantage of pair programming is crucial to success in CS51. If you feel that your partner has less experience than you, navigate more and drive less. Slowing down a bit and working together as a pair has the advantage of forcing you to explain ideas to solidify them in your own mind while improving your partner’s understanding. If you feel that your partner has more experience than you, take advantage of your partner’s expertise to explain to you what’s going on, benefitting both you and your partner.
Whenever you and your partner complete an exercise, take time to celebrate your accomplishment. High five. Fist-bump. Stand up and stretch. Applaud yourselves. The resulting endorphins lock in the knowledge. (I made that last bit up, but at least one study shows the effectiveness of high fives for improving motivation, task persistence, self-evaluation, and error fixation.1
By the end of the lab session, use git to commit and push your solution and submit the lab to the grading server at least once to save your progress and register your efforts.
After the lab session, continue to work on the exercises until completion. You can work alone or with a partner, the same one or a different one. You can continue to submit the lab to see how it does on the unit tests. But again, the goal is to understand how to get to the best solution, and why it works.
We hand out solutions to the lab exercises on the following day. If there are exercises you couldn’t do, read the solution, then put it away and do the exercise yourself from scratch.
Finally, do the labs again, repeatedly over the course of the term. This is the most important thing you can do to improve your skills, but the most counterintuitive. Why redo labs you’ve already done? Riding a bike around the same block, redundant though the activity is, still helps solidify the skill. The psychological theory of spaced repetition provides the empirical backing to this observation.2 This hint is especially useful for the earlier labs, since later aspects of the course build so heavily on the first few labs. Going back every once in a while throughout the term to redo the earlier labs is tremendously useful. You may feel like the lab exercises are trivial to do after a few times; you’ve merely memorized the answers. That may be, but having memorized the solution to a particular problem, you’ll find it much easier to solve conceptually adjacent problems.
Problem sets #
Problem sets are intended to exercise the lab skills in a more open-ended and larger scale context. Consequently, you’ll want to have a good understanding of the prior labs before concentrating on the problem set. I recommend making sure to submit the fully worked through lab before moving on to the corresponding problem set.
We hand out the problem set specifications and code well before they are due. Your best strategy for doing well on the problem sets is to start work on them early. Working over more days, but less time per day, yields better results. (Again, the literature on the psychology of education bears out that “spaced study” is more effective than “massed study”.) At the least, you’ll want to read over the problem set specification as soon as possible, so that you’ll know what kinds of things to look out for in readings and labs. Establish your own deadlines for the problem set and stick to them.
Don’t just think about the problem; think about your process. This metacognition can be extremely valuable. You have a bug. You can think about what’s wrong with your code. But better is to think about what’s the best way to figure out what’s wrong with your code. What options do you have? Which one is best to try first?
Develop unit tests. The time spent will not be wasted. First, it forces you to think about the functionality you’re trying to implement. Unit tests not only allow you to test your code; they provide insight into what the code should be doing in the first place. Second, it forces you to subdivide that functionality into pieces that can be independently tested. Unit tests should go in a separate file, named in a way to associate it with the file it’s testing. Don’t forget to run the unit tests any time you make changes to the code they test.
Use git. Commit early and often. There’s no such thing as too many commits, only too few. Push to the remote repository every time you commit so you’ve effectively backed up your work offsite.
For pair problem sets, if you choose to work with a partner (and I suggest you do), use the pair programming method you’ve been practicing in labs at least for part of the time. In particular, don’t just divide up the parts of the problem set in a “you do part 1; I’ll do part 2” way.
Even if you don’t work with a partner, it can be valuable to talk with others about how to make progress on the problem sets (and labs and final project for that matter). You need to take care not to fall afoul of the collaboration policy: No sharing of code.
Eventually, your code will seem to “work” in the sense of getting the right answer or behavior. You may think that once your problem set seems to work, you’re done. But really, once you’ve gotten things working is the best time to start the problem set. Mark Twain said that “The time to begin writing an article is when you have finished it to your satisfaction. By that time you begin to clearly and logically perceive what it is you really want to say.” The same holds for code. As E. M. Forster introspected, “How can I know what I think till I see what I say?” Once you’ve got a working solution, it’s time to see what you said so that you can figure out how to say it better – along the several pertinent dimensions the course looks at.
So, once you’ve got a working version, commit, push, and submit to the grading server. Then go back and review your code to see if there are parts that could be refactored or revised to improve the design. Are there parts that could be broken up in better ways? Could functions be written more generally? Are failure modes or exceptions handled well? Have you tested the code well?
From a concrete syntax point of view, how is the coding style? Neatness counts. Have you used consistent and standard conventions? Is the code well organized and neat? Is the code fully commented and are the comments clear and well written? Do they explain why rather than what?
In summary, your goal is not (merely) to get the “right answer”. Your goal is to make it as easy as possible for your grader to give you an A.
As mentioned above, the course is intended to impart both conceptual knowledge and practical skills, with the readings serving as the primary mechanism for the former and the labs for the latter. Exams are evaluative of that understanding, so the best way to study is to do the labs again. Your effort spent redoing the labs is not redundant, just like riding a bicycle one more time around the block or doing one more lap in the pool wasn’t redundant when you were learning to ride a bike or swim. As much as my ego would be benefited by your watching the lectures one more time, doing so will improve your performance on the exam little to none. Instead, do the labs again, do the exercises in the book (many of which were taken from past exam questions), or write your own lab-exercise-sized problems and work on those.
Getting help #
Because the material in CS51 builds on itself, it’s especially important to understand the concepts and skills exposed in the early parts of the course. It’s much harder to catch up in the course than to stay caught up. Consequently, if you feel you’re not fully understanding things, try to get help as early as possible. Some time spent in a TF’s office hours (or mine!) or working with an ARC tutor early in the term can prevent a snowballing of falling behind later.
The upshot #
To me, the ideas in CS51 are both beautiful and useful. I want everyone to understand and appreciate them. If you’re having trouble with them, please let me know so I can work on getting these ideas across.
Bradley J. Morris and Shannon R. Zentall. “High fives motivate: the effects of gestural and ambiguous verbal praise on motivation.” Frontiers in psychology, volume 5, number 928. 27 August 2014. ↩︎
Nicholas J. Cepeda, Harold Pashler, Edward Vul, John T. Wixted, and Doug Rohrer. “Distributed practice in verbal recall tasks: A review and quantitative synthesis.” Psychological Bulletin, volume 132, number 3. 2006. ↩︎