One of the things I enjoy most about teaching is creating course projects that challenge students outside of the classroom, where they can engage with the material on their own terms. A well-crafted project emphasizes and expands on the course’s core intellectual topics–it focuses the students’ attention and lets them apply the course concepts in a tangible way.
For the students in my Compilers and Interpreters course (CIS 341), I have created an integrated, team-based, semester-long project. By the end of the term, the students have built, from scratch, a fully-functioning compiler. A compiler is the fundamental tool that a programmer uses to translate human-readable code into the binary instructions understandable by a computer’s CPU. This project resonates with students because they use compilers routinely—building one themselves is enlightening and empowering.
There are many benefits to this approach. We tackle an ambitious engineering task, where each step builds on the previous one. Along the way, students grapple with development issues that arise at scale; they integrate knowledge from throughout our curriculum; and they gain a strong sense of accomplishment.
Structuring the course around a single project made me rethink its organization so that the lectures cover each component’s material when we need it. In return, the project provides a clear narrative arc, so that even the most theoretical parts are well motivated, which keeps the students engaged.
Using a large project is not without cost. The stakes are higher (for both me and the students), and it takes significant time and energy to develop. Nevertheless, I think (and, based on student feedback, they seem to agree!) that the extra effort is worth it.
As I reflect on what I’ve learned from project-based teaching, the following lessons come to mind.
Set expectations appropriately
It’s important that students know what they’re signing up for. The CIS 341 students are mostly juniors and seniors who are looking for a challenge; it’s a “projective elective” a class which is not required, and in this case with a reputation for being difficult. I make it clear up front that the project will be time consuming yet rewarding. I enforce the course prerequisites and meet with students to make sure that they have sufficient background. My grading scheme reflects the course priorities—the project makes up about 70% of the overall grade.
I tell the story of the whole project on day one and reiterate it many times throughout the semester. My goal is that students always know where the project is going and how what they’re working on connects to what we’re doing in lecture.
Modularity and abstraction are key
I divide a semester-long project into six to eight well-defined parts that fit together and yet can be graded separately. Decomposing it in this way is, of course, the hard part of designing the project. The engineering principles of modularity (how to divide a system into parts) and abstraction (how to describe the boundaries between those parts while hiding irrelevant details) are crucial here, and indeed, are part of the lesson I’m trying to convey. Luckily, for the material in CIS 341, computer scientists have distilled decades of experience into a “design pattern” that suggests how our project should be structured. This gives me the opportunity to explain why the project is set up in the way that it is, and allows me to explicitly talk about the “meta lesson” of how to break large tasks into manageable chunks.
When developing the project, most of my effort goes into defining clear specifications of what each piece is supposed to accomplish. In lecture we consider various possibilities for how the specification might be implemented, but the students are largely left to their own designs when it comes down to building the parts.
After each phase, I provide students with feedback that gets them going in the right direction, so one misstep does not derail the whole project.
Set a good pace
A semester-long project is a marathon, not a sprint. I try to arrange the workload so that students can make consistent progress throughout the term. I allow generous time for students to familiarize themselves with how the parts of the project relate to one another, often providing information about subsequent parts well in advance of when they will be assigned. I aim for the denouement of the project–when all of the pieces fit together for the first time–to be about three quarters of the way through the semester. This allows the last stages to be more about polishing, reflecting on the project as a whole, and creative extensions, rather than just about getting it finished. Since student workloads pile up during the last week of class, they also appreciate having the peak effort earlier.
I incentivize students to start early and give them a lot of feedback along the way. One effective way I’ve found is to require (for a grade) that students post challenging tests or examples to the course’s web forum. Those examples can be used by the entire class for troubleshooting and clarifying the specifications, which helps everyone gain a deeper understanding. I insist that each new example be substantially different, so students who start early have a leg up on procrastinators. This kind of small nudge is particularly effective because it plays to the students’ competitiveness, but in a way that helps everyone.
I’ve found that small teams of two or three students work better than individual projects or larger groups. Small groups let students share the work, but still feel a sense of ownership. This helps keep them motivated, and makes assessing their understanding for the purposes of grading easier. I explicitly talk about good collaboration practices, and encourage the use of appropriate collaboration technologies (GitHub, Slack, etc.), which students are eager to use.
I also try make each part distinct in terms of the experience it evokes– I never want students to feel that they are just doing “more of the same,” even though it all boils down to coding. This variety of challenges keeps thing fresh and creates a sense of anticipation, which helps drive their interest throughout the semester.
Revise, revise, revise
The course staff and I devote a lot of time to testing our own solutions to the project, documenting its specifications, and improving its presentation. This pays off not only by ironing out problems before the project is released, but also by creating test cases and examples that we use to give feedback to students continuously throughout the semester.
Conversely, I ask the students to give substantive feedback about each part of the project, so I can learn how engaging, challenging, and effective they found it. Their insights about what worked, what didn’t, what was unclear, and what was rewarding or boring have helped shape the project. It takes a fair amount of effort to update the project significantly every time I offer the course, but doing so mitigates against cheating, and, more importantly, keeps the material fresh. I find myself learning something new each time. This keeps me excited about the course, which (I hope!) translates into an enthusiasm shared by my students.
Steve Zdancewic is a professor in the department of computer and information science in The School of Engineering and Applied Science.
Dr. Zdancewic won the Lindback Award for Distinguished Teaching in 2018.
This essay continues the series that began in the fall of 1994 as the joint creation of the College of Arts and Sciences, the Center for Teaching and Learning and the Lindback Society for Distinguished Teaching.
See https://almanac.upenn.edu/talk-about-teaching-and-learning-archive for previous essays.