Preamble for those interested in how I made it through 8 years of computing education research without knowing how to program:
When I started my research career in psychology, I knew nothing about computer science. I had chosen to do my PhD with Richard Catrambone, a world-class cognitive scientist doing cool work at the intersection of cognitive psychology and educational technology. In my first month, I agreed to be a research assistant on a project about applying educational psychology to computing education between Richard and Mark Guzdial, a renowned computing education researcher. To me at the time, Mark was just some professor, and computing *probably* had to do with computers. I still remember our first meeting when Mark asked me if I had any programming experience. I said I had worked a little bit with HTML (and not that it had to been to customize my MySpace page). He gently told me that didn’t really count for what we were doing, and I tried to figure out why but couldn’t. That’s how little I knew.
So how on Earth have I conducted computing education research from that day forward? Partly with fearlessness stemming from sheer ignorance, but mostly with tons of help from people with loads of experience and knowledge about computing and teaching computing. While at Georgia Tech, I worked with Mark, Briana Morrison, and Barbara Ericson, who each have more computing education knowledge than any one person has a right to. Working with them, the most valuable perspective I had was as a novice. I could empathize with learners because I knew just as little as they did.
Despite discovering the power of computing and advising everyone else to take a programming class, I didn’t take a class myself to protect my novice status. Of course, I learned bits and pieces about programming concepts and applications throughout the years, but I never tried to learn systematically nor experienced much programming in text-based languages. Then Briana Morrison, Adrienne Decker, and I got a grant to build instructional materials for an entire intro programming course. We used the subgoal learning framework, which requires a novice and expert to conduct a task analysis for problem-solving procedures. As the novice, my job was to learn a semester-worth of programming procedures for the sake of science. After that, I couldn’t claim to be a novice anymore, and I could finally take my first programming class. David Joyner was kind enough to give me access to his online “Introduction to Computing in Python” course that he uses at Georgia Tech, and I’ve been working on it over the weekends this summer. As an instructional designer who focuses on online learning, I can tell you it’s a high-quality course. None of my reflections should be interpreted as disparaging David’s awesome work.
What I Learned about Programming Education from Learning to Program
In this blog post, I’m trying to only represent my perspective. My perspective is as a person taking an introductory programming course who knows a lot about introductory programming concepts but little about writing programs using text-based programming languages. It’s an anecdotal and fairly particular perspective. If you’re looking for more evidence-based or generalizable information, I’d recommend Robins, Rountree, and Rountree (2003), Pears et al. (2007), or Luxton-Reilly et al. (2018) as excellent literature reviews of introductory programming education research. Some of my experience isn’t going to apply to the typical learner, but I imagine if I struggled with something then true novices probably would too. Perhaps as computational thinking or concept-focused computing education becomes more prevalent in K-12, my experience will become more common.
1. Applying programming concepts was easy, except loop indexes and functions
Conceptually understanding variables, operators, data types, conditionals, and loops is super easy when you’ve been exposed to these concepts for years, you’re reasonably well-versed in their common misconceptions, and your Twitter feed is full of debates like whether a box is a good analogy for a variable. Granted, I first learned about these concepts by hearing others talk about them, and I didn’t find them hard to understand because they’re similar to many math and logic concepts. Learning to apply them can include some particulars, especially when you get into data structures and mutability, but learning the rules wasn’t hard. Even new concepts were still pretty easy to use, like dot notation for using methods. However, two things halted my momentum despite understanding them conceptually: for loop indexes and functions.
We first learned for loops using i as the index, such as
This made sense to me. The computer recognizes i as the index. Then we started naming these indexes to self-document the code, especially in for each loops, such as
This did not make sense…how would the computer possibly know what number was supposed to do? It’s just a word that describes the type of information in myList. Using myList made sense because that was defined outside of the loop. So far in class, we’d learned about variables and that you can’t use variables without initializing them first. Perhaps that’s why I felt like I needed to initialize my loop index to make it work. It took a while to realize that whatever comes after for would be treated like i by the computer.
For functions, I can’t decide whether defining and calling functions should reduce cognitive load while programming (because you can go process this mini-program separately) or effectively double cognitive load (because you now essentially have two programs to keep track of). Regardless of what they should do, calling functions that I had defined doubled my cognitive load. Dr. Joyner always did an excellent job of showing how the computer would process a program, replacing variable names with values, simplifying operations, etc. to walk through the program one step at a time. For whatever reason, I never really internalized that process until I struggled with functions. During the first half of the class, I could usually keep in mind the different parts of the program simultaneously. When we started using functions, I had to develop a new strategy that focused on how pieces connect to each other and how to pass my attention between different parts of the program.
2. Deciding which programming concept to use was harder
When we learned a new concept that was followed by a practice problem, it was usually pretty easy to apply the concept to the problem. However, when working on problem sets after learning multiple options for solving problems, decided which concept to apply was considerably harder. For example, when working on the problem set for conditionals, I often wasn’t sure whether to use an else-if or nested conditional. I finally had to put away the problem set and use self-explanation to come up with a strategy for when to use what. This is precisely the type of knowledge that becomes automated over time and makes it difficult for instructors/experts to articulate strategies that seem like intuition to them. In lieu of an instructor’s explanations, self-explanation and peer-explanation are effective strategies that learners can use, but I didn’t remember to use self-explanation until halfway through the problem set. Moreover, research suggests that up to 90% of students need to be reminded to engage in these kinds of learning strategies.
As an aside, I decided that else-ifs were best when you had mutually-exclusive conditions, and you should start with the most exclusive option. On the other hand, nested conditionals are best when multiple conditions must be satisfied, and you should start with the easiest to satisfy option. Yes?
3. The math and logic was consistently the hardest part
Deciding which concept to use was sometimes difficult, but using math or logic to fill in the body of the program was consistently the hardest part of any problem. For example, while writing else-ifs, I had the most difficulty figuring out what are the mutually-exclusive conditions and which is most exclusive. It’s the problem-solving part of programming in which you translate what you want into something the computer can execute. This surprised me because I always hear people talk about how to help students understand concepts or write syntax, but I rarely hear them talk about practicing math or logic skills except in K-12 computational thinking instruction.
I found the math and logic difficult as a person who has always been good at math and who learned formal logic in a semester-long class in college and as part of a computational thinking class right before I started the intro programming course. I remember the point in college when the clouds cleared, my thinking shifted, and everything started making sense, which logic professors say is a normal experience. During the computational thinking class, I thought that this logic stuff was so easy. But to actually apply it to programming was more taxing than deciding which concepts to use or making sure the syntax was right.
4. Reading code is a different skill than writing code
As surprised as I was to find that the math and logic was the hardest part of writing code, I was more surprised that reading someone else’s program and interpreting their math and logic was also really difficult. I’ve regularly heard people explaining their code and describing what each line or set of lines achieves, and that was always easy to follow with limited programming knowledge. Reading code on my own without someone describing their process, even with comments sometimes, was shockingly difficult. So difficult that I found myself only wanting to look at the correct/optimal answers to problems if I had struggled with the problem or was uncertain whether I’d taken the most concise approach. Reading someone else’s code, even for a problem that I had just solved, was that difficult.
This realization is particularly poignant for me as someone who works with designing worked examples for programming instruction. Worked examples are problems with the worked-out solution–exactly what I had trouble reading. My research group uses subgoal labels to add procedure-focused instructional explanations to worked examples that help students see past surface details of the problem (e.g., that the loop is finding the sum) to the problem-solving structure that they are learning (e.g., how to use a loop). I’ve always thought the benefit of subgoal labels was helping students filter out the extra information that is important only to the problem and not to the procedure. Maybe part of the value, though, is also orienting the reader to the logic used for each line of code or set of lines.
5. Debugging someone else’s code is much more frustrating than debugging your own
Related to my difficulty in reading someone else’s code, debugging someone else’s code was my least favorite activity. Students who are caught cheating in a programming class should be forced to debug others’ code as punishment. It reminded me of this gem… At least when I’m the murder, I already know the layout of the crime scene. When I debug my own code, I know what I was trying to achieve, what’s most likely to have gone wrong, and the syntax errors I’m prone to make. When I have to debug someone else’s code, I have to figure out how someone else tried to solve the problem while simultaneously not trusting what they’ve written.
Part of the benefit of giving a learner partial or buggy code is that they don’t have to write it from scratch, so the programs can be more complex and authentic. There’s also evidence that correcting erroneous examples can be beneficial, even though students find it confusing and frustrating. In my student perspective, I found modifying longer, more complex programs valuable and rewarding, but not debugging them. The debugging practice I found useful was fixing easy, rote errors earlier in the semester to practice remedying different types of errors.
Perhaps the most important takeaway from this post is that some parts of learning to program can still be challenging for a person who has studied programming education for almost a decade. Even for a PhD-holding learner who studies programming education and learning strategies like self-explanation. And who has enough self-regulation skill to take notes about her experience of learning to program while taking the class. I’m not saying this to brag–I’m saying I had everything going for me, and there were still parts that I struggled with throughout the course. I don’t expect that everyone will have the same experience of what is easy or difficult, but the point is that it’s normal for things to be difficult. I plan on using this experience to help convince the K-12 teachers who I’ll be working with in the Computer Science Endorsement program that they shouldn’t be discouraged if they struggle at times. It’s not them, it’s just hard.
Luxton-Reilly, A., Albluwi, I., Becker, B. A., Giannakos, M., Kumar, A. N., Ott, L., … & Szabo, C. (2018, July). Introductory programming: a systematic literature review. In Proceedings Companion of the 23rd Annual ACM Conference on Innovation and Technology in Computer Science Education (pp. 55-106). ACM.
A summary of Luxton-Reilly et al. (2018) can be found on Austin Cory Bart’s blog.
Pears, A., Seidman, S., Malmi, L., Mannila, L., Adams, E., Bennedsen, J., Delvin, M., & Paterson, J., (2007). A survey of literature on the teaching of introductory programming. In Working Group Reports on Innovation and Technology in Computer Science Education (pp. 204-223). ACM.
Robins, A., Rountree, J., & Rountree, N. (2003). Learning and teaching programming: A review and discussion. Computer Science Education, 13(2), 137-172.