
During the last decade, I have worked with many fresh, and senior programming professionals from different backgrounds including software engineers working in the industry, researchers and academicians. Unfortunately, a lot of them struggle to write a self-worthy and maintainable piece of code.
After joining as a professor at an engineering University, the urge to understand the reason behind it and find better methods to teach the art of programming grew higher. This led to a lot of experimentation through lectures, programming assignments and individual mentoring sessions. We also tried an informal community-led approach by organizing club meetups, competitions, quizzes etc. to penetrate deeper into the mindset and experiences of students towards programming.
This article presents the experiences gathered from these activities and is primarily structured to help teachers improve the outcomes of their introductory programming course. However, some key insights included are helpful for every new programmer working on building his/her programming skills.
TLDR: This article is a bit long and readers looking for a brisk read can follow the highlighted (in bold) text to quickly read through it.
Going Off-Roading
Generally while teaching concepts of programming, it is a common practice to use pre-written and well-tested code snippets in the classroom. This helps the teacher to bring in well-prepared examples that are perfectly suitable for the concept to be explained. It also helps the instructor focus on the concept rather than the correctness or suitability of the example. Analogically, it is a smooth highway ride where nothing can go wrong because you are well-prepared.
But that is not true practically. There are few programmers in the world who can write flawless code on the first attempt every time. When the student has to write a piece of code for a problem given in the course assignments, there is no well-thought, pre-written, perfectly tested code to work with. The student will have to write each statement and every character on his own. And once a student daringly writes the required code, ding!, there comes a compilation error.
But .. that did not happen during the class. How is it happening now! Is my code correct? It doesn’t seem to work at all! Atleast if it runs once! I can’t write a program on my own…
A sequence of thoughts questioning his/her ability to write code — starts haunting the student. Some can overcome this on their own, some require external support and some just choose to STAY AWAY from writing code again!
On the contrary, if the teacher chooses to take live coding sessions during some of the lectures, it might possibly lead to uncomfortable situations and a few more lectures, but can bring confidence to the students. Just like when you are teaching a teenager how to drive a car, you would sometimes take the steering wheel and demonstrate how to handle the steering, control the acceleration, dodge obstacles, apply sudden or gradual breaks etc. on rough terrain, a good teacher must go off-roading in programming as well with his/her students.
Building the Rome is Tough
Building a city, or for that matter any system, from scratch requires patience, expertise and strong determination. During the starting days of learning programming, a learner has to grasp many unknown and complex computing concepts such as the compiler, languages, tokens, memory model, data types etc. Further, he/she has never been exposed to the concepts of lexemes, syntactic structures, semantic rules, language-specific delimiters, storage classes etc. Although it is not considered to be necessary to have a detailed understanding of these topics to understand the basic concepts of programming, in practical, a student has to learn to remember and use these terms and constructs for writing a simple “Hello World” program. Have a look at the following Hello World program in C language —
#include <stdio.h>
int main()
{
printf("Hello, World!");
return 0;
}
It includes the usage of a standard library, a function, data types, return values, literals, call-by-value function arguments, etc. Asking students to write a code involving so many concepts which have not been taught to him/her is over-expecting the existing knowledge and skill set of the students. Instead, if we could provide boilerplate coding assignments where the students are required only to focus on and use the targeted programming concepts, it can help increase student efficiency and interest towards more practical endeavours. See example below –
#include <stdio.h>
int main()
{
printf(____WriteAStringHere___);
return 0;
}
And before we question the practicality of boilerplate codes, they are used by professionals more often than not. As per recent developments, more and more IDEs are utilizing AI to offer boilerplate codes that can help reduce the effort of a programmer. For more info on boilerplate, please refer this article.
Peer Instruction
Importance of Peer Instruction has been tested time and again! One Teacher in a programming course containing more than sixty and sometimes 100 students at the same time, with generally 2–3 such sections, that too in the first semester of studies in most universities, can only deliver the content and provide measures of self-assessment. This truth cannot be suppressed through any ICT tools or techniques. The teacher cannot identify the thought processes, missing links in the chain of concepts and fear of failure that a student experiences until the student is ready to standout and approach the instructor or a TA (mostly not present in mid and low tier universities) during a laboratory session or beyond instruction hours. Unfortunately, that does not happen too often.
In such as condition, it becomes crucial to seek the advantages of Peer Instruction, a pedagogical tool that allows for organized exchange of ideas among peer students in a Course. It facilitates easier and comfortable mode of communication while reducing the impact of prior knowledge and inherent fear of standing out among students. However, a teacher has to act as a mediator or guide to promote Peer Instruction effectively and positively.
Asking the right questions and allowing moderated interaction among students during a lecture/laboratory session requires a little bit more will and effort from the instructor but gives exponential results. Teachers interested in learning more can jump to this video on Youtube where Prof Sahana Murthy, CDEEP, IIT Bombay explains the concept, pros and how-tos on using Peer Instruction in a classroom. There are many resources available online to help teachers adapt PI, especially in CS courses.
Exposing Your Errors
Facing one’s insecurities is a skill that everyone needs to develop to learn new things and accomplish great results. But more often than not, completely adhering to the peer-pressure and societal expectations, professors do not usually like to enter a situation where their insecurities may be exposed, especially in front of students.
And ironically, it may be the right thing to do! Because in the context of programming, this translates to a teacher/instructor getting stuck on a compilation error, runtime exception or unexpected output. Most professors would avoid such a situation and generally take tested code snippets to class or never hit the Run command during a class. On the contrary, such events can be taken up as an opportunity at times to teach students what syntax errors are, what they mean, how we can fix them, how runtime errors can still break our successfully compiled code and why debugging is the best skill a good programmer has. Above all, the student will learn that it is okay to get errors when you compile your code for the first time or the nth time for that matter.
If you cannot take this risk, due to any administrative, societal or psychological reasons, prepare a well-rehearsed act where you actually implant a certain bug in your code and then demonstrate how it is perfectly fine to get compilation errors irrespective of whether you’re a new or a seasoned programmer.
Predicting Outputs
During lectures in programming classes, we often tend to use codes followed up by their outputs to explain concepts and syntax of constructs. While this approach tends to demonstrate the cause-effect relationship between the code and its output, it usually does not enable the student to learn how to use those constructs. The primary reason behind this is the lack of context or lack of attention from the students. Lack of context can arise out of many situations where a student has missed a previous lecture or could not relate to the problem being solved. Lack of attention can also have many reasons where most common being a higher cognitive load during long programming lectures.
Alternatively, if the code is shown after explaining the underlying concepts without corresponding output, and students are asked to predict the output of the code, followed by calling random individual students to show/explain their prediction, can have a much larger impact. Primarily due to the fact that the student will now have to fill in the context and work out the problem himself. Also, routinely using this strategy leads to students having higher attention during classes, since anyone can be asked to defend their learning in front of the whole class.
However, it becomes a prime responsibility of the teacher to accept the prediction of students without any expectations, and if the prediction is incorrect, it should follow with clarification without pointing out any personality traits or prior impression of the individual.
Alter-Programmer
Programming requires critical thinking, organizing thoughts and converting them into code while utilizing the knowledge of syntax rules and programming constructs offered by the underlying language. Due to this, most incoming students to an introductory course on programming feel exposed to a fear of failure & bad impression. Further, due to the new unknown environments and lack of trustworthy friends in freshman year, most students don’t share these fears, and it takes a heavy toll on their emotional health.
Generally, writing good programs involves reading programs and being critical about them. A novice programmer lacks these two skills, and when code is written by themselves, frustration and fear of failure lead to further loss of critical mental efficiency. In such a context, having an Alter-Programmer (derived from the word AlterEgo which means someone who knows you better than yourself), can provide both emotional and critical support. At an initial level, an alter-programmer does not have to be a best friend or very good at programming skills, rather, a classmate from similar background and knowledge can provide a better source of emotional support and criticism required while doing programming exercises. It can be taken up as a good practice to form pairs during laboratory sessions in an introductory programming course. However, such practice, if continued over more advanced programming courses or during later semesters of study, can induce a lack of confidence and inferiority in the weaker student of the pair.
Beware of Transfer Learning
Transfer Learning is the ability of an intelligent system to apply knowledge and skills learned from similar problem domains in the past to unknown or relatively new problems. Humans, credited for having the most advanced forms of General Intelligence, are very good at transfer learning for e.g. being able to use past skills when trying new cuisines while cooking, being able to understand different accents of the same language, drawing using a stick on the sand surface while holding it in a same way as a pencil and so on.
During programming courses, teachers often bring real-world examples and references from other programming languages to utilize the transfer learning and connect the students to the concept being taught. It works out very well for some students who either have prior programming exposure or have a higher IQ w.r.t. transference of knowledge. However, novice programmers can’t separate the understanding of concepts such as data structures, loops and references from the underlying syntax and language-specific details. Moreover, when students can closely relate to a particular example, they may try to apply the same to other programming constructs where the example may not be relevant.
To help them in building a conceptual understanding, multiple examples (as many as 4 or 5) from known contexts should be explained first before linking them to the syntactic elements of the language being taught. This way the separation of understanding from syntax can also be encouraged, leading to the development of an algorithmic approach towards solving programming problems, which, in addition, is also crucial for designing bigger software systems.
Role of Abstraction
Abstraction is a crucial skill for programmers to allow looking at the big picture and promote reusability. Most experienced programmers, such as teachers of a programming course, are very good at creating and applying abstractions.
In fact, looking at most programming exercises, you can easily observe that most of the problems are abstract in nature such as calculating factorial of numbers, comparing integers etc. This encourages students to build their skills in applying programming knowledge to problems without any context.
But when a student fails to understand abstraction, primarily due to lack of experience, and asks a follow-up question, the natural inclination is to give examples with context. Some examples of contextual explanations are —
1. Using Olympic race tracks to explain loops,
2. House Addresses to explain pointers,
3. A cupboard to explain structures etc.
I have personally found that such examples are not successful most of the time, mainly due to the following reasons –
1. Student may not be able to relate to the context but is afraid to ask for clarification for e.g. when a student has not seen an Olympic race track.
2. Students may over-relate to the context leading to distraction. For e.g. a student would start thinking about the items in his/her cupboard and what he/she needs to buy to put into that cupboard.
3. Students can’t establish a link between the contextual example and earlier non-contextual explanation due to a lack of separation of a concept from context.
4. Students accept the explanation mid-way and try to quickly return back to their comfort zone where the whole class is not looking at him/her.
It is recommended to stick to any one of the strategies — to use abstract or contextual examples at a time. When using contextual examples, at least 2–3 different examples must be used to explain the concept to allow separation of the concept from context. Finally, ensure that the student who took the courage to ask for the explanation does not feel like the odd one out. Techniques from active learning such as Think-Pair-Share can be helpful here.
Debugging is Too Early
Debugging is a skill that every programmer needs to survive. Developing the ability to understand an error or warning, and tracing it back to its source, is an investigative journey that requires patience, attention to detail, experience, persistence and high levels of confidence. Being able to find the part of code where the unexpected is happening requires a clear understanding of what every line of code does and its syntactic accuracy other than being able to use debugging tools or knowing where and what to echo during execution.
Every teacher who teaches an introductory programming course knows that this is not the case with most of the students. Students primarily use a trial and error strategy while writing code, where they are neither confident nor totally understand the code written by themselves. Expecting a student to be able to debug the semantic and runtime errors on their own during an introductory course is asking for too much — too early. Moreover, an editable online document can be used to list most common syntax, and semantic errors and their solutions can be maintained to ease the debugging at such a primary stage.
Cognitive Load of Decomposition
Writing programs for a problem of sufficient complexity requires decomposing the problem into a set of header & code files, data structures, classes/functions etc. Further, during later programming courses, students may also need to use certain standard/public libraries to accomplish the given task. Academicians and Professionals around the world have devised architectural styles, programming paradigms, design patterns and standard practices to help them carry out this task. Even during undergraduate CS education, every university usually has a course on concepts of design and decomposition generally taught later during the third and fourth years of study.
Decomposing a bigger problem into a required set of individual program components is equally heavy on cognitive load for a novice programmer as the task of decomposing a software architecture for an experienced software engineer. It is recommended to delay the exercises requiring decomposition to later courses where the students are comfortable with the syntax and semantics of the target programming language. During introductory courses, such problems which require heavy decomposition should be either avoided, or a reference decomposition (such as function prototypes, pseudo-classes or diagrammatic representation of structure) should be provided along with the problems.
Verdict
Above listed are some of the many observations and learnings that the teaching assistants and I have noted during our interactions with the students. Most of the solutions presented here are driven by intuition and are not backed by any experimental study. However, these insights can actually help new or seasoned teachers teaching introductory programming courses at schools or universities achieve greater success within their classes.
If you’re interested in discussions around programming or CS pedagogy, you can always connect with me or leave your thoughts in the comments. I will be happy to hear from you!