Here is a collection of my thoughts and experiments on better teaching programming. This article is written in the context of the Mathematics Lab with Python taught in colleges in Bangalore/Karnataka. I have tried almost all of it: and I have seen them building confidence among students to write codes themselves.

The most adopted strategy by teachers is to write codes line-by-line, which does the job – some of these tricks are either an improvement to the process or an alternative to it. I’m a staunch believer in teaching students to “learn/create ideas themselves” than filing them with content or leaving them to figure it out themselves, and hence these tricks in essence are about equipping students to improve their programming skills and confidence to do it themselves.

For those who are missing the context: Mathematics lab session in Bangalore has this pattern:

- The teacher introduces the concept (usually already taught in the theory class).
- Students manually solve the problems assigned.
- The teacher discusses the program.
- Students execute it to verify the manual solution with programming.

# Building Algorithmic Thinking

Algorithmic thinking in essence is all of the programming. Languages are mere tools to express the algorithm in a way computers can understand. Programming for me is about solving the puzzle, the algorithm, and then things get trivial.

That is why it is more important to emphasise the importance of algorithms and train students to write algorithms. That should be set as the first trigger when a programming problem is proposed to them – while a line-by-line explanation does not encourage them to do it. This involves a few steps:

- To write the algorithm, we need to figure out the variables we need.
- Then start decoding the algorithm from the manual solution. Even though we do have an algorithm embedded in the manual solution, it will be a bit vague.
- Write the algorithm formally and fine-tune it.
- “Translate” the algorithm to program.
- Execute and check for errors.

While it might take a lot of time in the initial sessions, you will see that as the sessions progress, you can trust students one step at a time, starting with step 5. Also, I would intentionally introduce some errors in the process that they will come across when they execute the program: helping them to learn to debug the code.

# Programming: An Intuitive Process

Picturing programming as an intuitive process always appeals to those averse to programming. Instead of using external functions, I use attributes. For example, Sympy’s matrix has the trace, determinant, inverse, etc. as an attribute. For example, if M is a sympy matrix, they are told how to find the trace using M.trace(). Then they are asked to guess how to find the determinant – guesses will flow in, and after a while, they will (or I’ll guide them to) M.det().

Going by Polya’s theory on “being a better guesser”, when they are asked to find the inverse, the majority usually pins on M.inv() naturally. Extending this, they were able to guess how to differentiate, integrate, find the limit, etc. (as the attributes to sympy expressions) in later lab classes by themselves.

This approach was tested multiple times on first-year UG, PG, and final-year UG students, and those hesitant/scared of programming eventually started responding well to it.

# Design Your Custom Codes

I was fascinated by the idea of Gamifying learning. This partially falls into that category. After building the algorithm, I gave students a Microsoft Form which assisted them in translating each line in the algorithm. Students were prompted to choose one of the 3-4 correct Python lines to make their own code. All options were correct – hence though they were initially hesitant thinking it is a test, they soon got into the wagon once they were told each question is “different ways of doing the same thing”. Upon answering each question, they had to copy that Python line to their observation note, and finally, execute the compiled code (and submit it with the Form before proceeding to the next question.

For the second question, they are given a choice: either do it the same way, with guidance or try to write the code themselves given the algorithm. Though most students chose the guided solution, a handful chose to write the code themselves. The third question insisted everyone write the code themselves and submit it with the form.

Further, they had to submit the final code with the form, helping us glance through their choices and final code. Here is a sample (Surface/Volume of a Surface): Click here for the form.

This has many advantages. It introduces the students to multiple ways of doing the same thing. It also helps them think through and see how the algorithm is broken down and translated. While they are figuring out the code themselves, I saw some testing all the variants to see how it works – building their intuition towards programming. One of the biggest advantages is, we could give personal attention to those who struggled, helping them through the process.

Targeted skill is the ability to “translate” algorithms to programs and begin to tweak/write their own algorithms. There should be many downsides to this; but the time I tried it, it went smoothly without many hiccups (except for one mistake I made in the options – I’m unsure if I fixed it in the form link above).

# Enhancements to Line-by-Line Explanation

There are some handy tools to describe a program/algorithm better. To name a few, problem statements, written descriptions, algorithms (already established earlier), and loop invariants.

## Problem Statements and Written Descriptions

Sometimes for smaller programs, it is sufficient to simply give the problem statement to clearly explain the code in the minimal words possible. The problem statement consists of two things: Input for the function/program and the expected output, described clearly. For example (Ring Theory),

**Input**: two elements x, y of Z_{n} and n**Output**: x ⊗_{n} y

```
def multmod(x,y,n):
return (x*y)%n
```

If required, one can add a line or two to describe the working of the code, if the function/program is longer, as illustrated below (Ring Theory):

**Input**: Set R, and modulo value n**Output**: Unity if it exists.**Process**: For each x ∈ R, we will collect those elements for which x ⊗_{n} y≠y in a set mult (mult = {y ∈ R│x ⊗_{n} y ≠ y}. If such elements do not exist, that is, x ⊗_{n} y=y for some x, then x is the unity and we will return it.

```
def mult_iden(R,n):
for x in R:
mult = {y for y in R if multmod(y,x,n) != y }
if not mult:
return x
```

This always helps the students who are dominant read/write learners to quickly connect with the process.

## Loop Invariants

Loop invariants are lifesavers in many ways! For example, an attempt to describe the loop in words has always turned out disastrous:

```
def mul iden(R, n):
for x in R:
flag = True
for y in R:
if multmod(y,x,n)!=y:
flag = False
break
if flag==True:
print(“The multiplicative identity is,”,x)
return True
print(“The ring has no multiplicative identity”)
return False
```

The explanation can be simplified using loop invariants. A vague loop invariant for the outer for-loop will be: During the execution of the loop, the variable flag is False if x is not the unity.

Loop invariants, in some cases, are lifesavers. While a developed mind can digest vague descriptions of loops faster, students often struggle to digest the role of the “flag” variable here. Loop invariant helps them associate the variable flag with the existence of unity.

## Written Description of a Loop/If… else Latter

Use of subcases while explaining the body of a loop/if… else ladder makes comprehension easy: for example (Ring Theory):

**Input**: Set R, and modulo value n**Output**: True if it is an Integral Domain and/or Field

**Process**:**Step 1** | Calculate the unity.**Step 2** | If Unity exists, then R_{1} = R – {0}.(Nonzero elements)**Step 2.1** | Print the unity.**Step 2.2 **| Collect all the zero divisors in a set zd = {x ∈ R_{1}│x ⊗_{n} y = 0, ∀y ∈ R_{1}}**Step 2.3 **|We know that in a finite commutative ring, every element is either a unit or a zero divisor. That is, units = R_{1 }– zd.**Step 2.4** | If there are zero divisors, it is not a ring, and we can print the zero divisors.**Step 2.5 **| Else, it is an integral domain. Since every finite integral domain is a Field (proved in class), it is also a field. We can print the units.

```
def is_id_field(R,n):
e = mult_iden(R,n)
if e:
R1 = R.copy()
R1.remove(0)
print("Unity is", e)
zd = {x for x in R1 for y in R1 if multmod(x, y, n) == 0}
units = R1 - zd
if zd:
print(R,"is not a integral domain, hence not a field.")
print("Zero divisors are",zd)
else:
print(R,"is an integral domain and a field.")
print("Units are",units)
n=5
R = {i for i in range(n)}
is_id_field(R,n)
```

# Fixing the Error

When a student encounters an error, encourage them to read through the error carefully. Start with the last line of the error which summarises it and then come back to the top line to spot where the error occurred. Python usually offers a detailed Trackback, helping us spot most of the errors quickly and fix them. But should be aware of the process – they should not give up when they get an error, rather, know how to understand the error message and fix it.

# Dealing with the “Experts”

Most of these techniques are trivial for those who are well-versed in programming. We usually give them challenges to optimise or interesting errors to keep them occupied. For example:

If the code has something in lines of importing functions from sympy and symbols from sympy.abc, the following trick will keep them occupied for a while:

```
from sympy import sin, pi
from sympy.abc import *
print(sin(pi))
```

The expected output is 0. But mysteriously, Python returns “sin(pi)”. This should raise the interest of programming nerds. The problem is, the second line replaces the variable pi (with the value of pi) with a symbol pi pre-defined in sympy.abc.

Call to help us optimise the code is another way to keep them occupied. Usually, the ternary operator in Python does the magic:

```
if a == 0:
print(“a is zero”)
else:
print(“a is nonzero”)
```

can be reduced to:

```
print(“a is”, “zero” if a == 0 else “nonzero”)
```

Simplifying programs using mathematical theory is another interesting challenge. For example the following code (fairly hard to explain):

```
def comm(R,n):
for x in R:
for y in R:
if multmod(y,x,n)!=multmod(x,y,n):
print(“The ring is not commutative”)
return False
print("The ring is commutative")
```

Can be rewritten using mathematical ideas (and list/set comprehension in Python) as:

```
def comm(R,n):
# Set builder {(x,y) | x,y in R, y*x != x*y}
noncom = {(x,y) for x in R for y in R
if multmod(y,x,n)!=multmod(x,y,n)}
if noncom:
print("The ring is not commutative")
else:
print("The ring is commutative")
```

Or even shorter:

```
def comm(R,n):
noncom = {(x,y) for x in R for y in R
if multmod(y,x,n)!=multmod(x,y,n)}
print("The ring is", "not" if noncom else "", "commutative")
```

I would choose to present the second version and ask students to write simpler or longer versions – that should keep the excited electrons occupied for a while.

In fact, the program in the previous section (to check if a given subset of Z_{n} with addition and multiplication modulo n is an integral domain and field uses a bit of theory; and is not the standard one presented in the class. The program presented in the class involves two separate functions to check if it is an integral domain and then to check if it is a field. I would challenge the students to “merge them into one” using theorems they learned in the theory class (every finite integral domain is a field). You can trouble them with the finer nuances: like why are we creating a copy of R? Questions of that sort help them fine-tune their skills, at the same time, keep them occupied.

That’s all from the top of my mind. Will keep expanding it if time permits! 😊

*P.S. Redesigning the lab sessions to include open-ended questions (something we did in PG labs) had tremendous positive responses. Not a bad idea to attempt to replicate it in UG labs!*