I just wrapped up grading and entered the final scores for the very first class I taught on my own last Monday. Up until now, I've been a student all my life, so stepping into the role of organizing and teaching a class to students was a completely new adventure for me. Although my responsibility was limited to the lab work section of the course, I was pleasantly surprised by the amount of freedom I had to shape the lecture content and design the assignments.
Since I started programming at the age of 12, I felt pretty confident in my ability to teach the class. My experience has been more closely linked to the industry side of programming rather than the academic perspective, which I only encountered during my time as a student. I remember feeling frustrated when I took this very same course at the same school. With that in mind, I was eager to craft a course that would have resonated with my younger self — the kind of class I would have wanted to take back then.
Most universities tend to emphasize algorithms and LeetCode-style problems in their introductory programming courses—even ones that bear the word "Advanced" in their titles. Despite being labeled as such, they're often just the second programming course in the curriculum, so I see them as introductory. From my experience in the industry, the reality of programming work hardly mirrors these academic exercises. In fact, I've observed that overemphasizing these elements might even be detrimental to the industry. With this in mind, while planning the course, I made a conscious decision to steer clear of these types of assignments.
The very first skill I wanted to weave into the course was the ability to break down large, complex problems into smaller, manageable parts and tackle them step by step. At this point in their education, most students haven't had the opportunity to build a sizable and intricate program. Their experience is usually limited to simple, one-and-done coding tasks, which may lead them to believe that's all there is to programming. To challenge this misconception, I structured each assignment to culminate in the creation of a relatively sophisticated and practical piece of software.
Take the first assignment, for example: the task was to create a program that processes a binary file filled with big-endian numbers, sorts these numbers into tuples, and then outputs them again in big endian format. Individually, the components – reading a file, converting big endian to little endian, sorting numbers, and writing to a file – are not particularly challenging. The real complexity emerges from integrating these elements. Without proper guidance, it's unlikely that a student could piece together such a program on their first try.
I was also keen on maximizing the time students spent hands-on with programming. This meant crafting tougher and more time-consuming assignments. However, calibrating the difficulty level proved to be quite the challenge. The programming skills of students entering the course varied widely – from those who were already adept at coding to those who barely knew how to start writing a program.
By the end of the course, I believe I honed in on a "sweet spot" for the assignments: start with a first and second part that are simple and introductory, allowing all students to get their feet wet. Then ramp up the challenge with a fourth part that's more demanding and time-intensive. Finally, for the last part, invite the students to get creative and incorporate features of their own design. This approach seemed to strike a balance, enabling beginners to still earn a decent score, while providing enough of a challenge to engage the more advanced students.
Promoting the use of debuggers was another priority for me. When I inquired about the most important programming skill to teach from the previous instructor of this course, the resounding response was debugging. Often, students hit a wall when their programs won't compile or function correctly, feeling at a loss for what to do next. C programs, in particular, are notorious for providing minimal feedback when they encounter errors, like the dreaded "segmentation fault."
To tackle this, I introduced students to using a debugger within VSCode, empowering them with the ability to pinpoint the exact line causing the trouble. I'm convinced this made a considerable difference, as it seemed to dramatically reduce the number of questions from students. Given the difficulty of the assignments, I was bracing myself to spend a lot more time assisting students.
That said, I did receive some feedback about issues setting up the debugger. So, moving forward, I'm considering allocating more time to debugger tutorials to ensure everyone is comfortable and adept at using this essential tool.
One aspect that caught me off guard was the varying levels of basic computer literacy among the students. Considering these were Computer Science department students, I had anticipated a relatively robust understanding of computers across the board. Yet, I found myself explaining the basics of directories and files, including the concept of a working directory to some.
It appears that with the ubiquity of computers in the past, a certain level of understanding was almost assumed. However, the increasing prevalence and restricted nature of handheld devices such as iPads may be contributing to a growing number of students who aren't familiar with fundamental computer concepts like file systems and directories. It's a bit concerning how the rising trend toward 'walled garden' computing models could significantly shape the foundational knowledge of future CS students. This is something I'll need to take into account and address more deliberately in my teaching.
In hindsight, another area for improvement relates to how I structured the class time. Following the model used in the prerequisite course, I designated the first 30 minutes for reviewing concepts, with the remaining hours intended for students to complete and submit their assignments. I had planned for the weekly assignment deadline to be 30 minutes into the lecture, so that students could approach me with questions in person if needed. Unfortunately, this created an unintended issue.
Many students concentrated so heavily on meeting this deadline that they couldn't fully engage with the concepts I was presenting for the next assignment. Moreover, it turned out that most students preferred to attend only the lecture portion and then chose to work on the assignments at home. Consequently, the TAs I scheduled to assist students often found themselves with little to do, as students hadn't dived deep enough into the assignments to have questions or had already left.
Going forward, I'm thinking it might be more effective to allocate a specific time slot at the start of each class purely for Q&A related to the previous assignment. After addressing any lingering concerns, I can then review the concepts needed for the new assignment. I might even reserve some time towards the end of class for students to ask initial questions. Given the nature of the assignments I've designed, which likely raise questions several hours into the work, this tweaked approach could provide a more supportive and efficient learning environment.
Determining whether I successfully taught this lecture is a tough call. I conducted a survey at the end of the course to gather student feedback, but with only half of the class responding, I'm aware it paints an incomplete picture. Many students expressed that the course was challenging and that the assignments required a substantial time commitment. While I had anticipated this reaction, given that rigor was an intentional aspect of my course design, I can't help but wonder if perhaps I tilted the scales a bit too much towards the heavy side.
However, there was also positive feedback. Some students appreciated the challenge and felt it significantly enhanced their programming skills. The varied responses point to a nuanced outcome: demanding coursework that pushes students but may also risk overwhelming them.
Equipped with these insights and my evolving perspective, I'm already brewing some ideas on how to refine the course. The goal for next year is clear—strike a better balance that maintains the course's rigor while being more accommodating of the different skill levels and capacities of the students. With any luck, these adjustments will lead to even more constructive feedback next time around.