The Exam Cell Grading Script That Misjudges Three Candidates
At a senior secondary school in Patiala, a Class 12 student writes a Python program for the exam cell. It reads three subject marks with int(input()), computes total = m1 + m2 + m3 and avg = total // 3, then grades the candidate:
if avg > 33 and avg < 45: print("Third") elif avg >= 45 and avg < 60: print("Second") else: print("First")
The school rules are: a candidate must score at least 33 in every subject, and the average decides the division - Third for an average of 33 up to but not including 45, Second for 45 up to 60, First for 60 and above. A candidate whose average is below 33, or who scores below 33 in any subject, must be printed as "Fail".
The clerk tests four candidates and gets the following output: A: 33, 33, 33 → prints "First" B: 32, 50, 50 → prints "Third" C: 20, 28, 30 → prints "First" D: 45, 44, 45 → prints "Third" (accepted as correct, average 44.67)
The exam cell now also wants the average printed to two decimal places on the same line as the division.
1. Explain exactly why candidates A, B and C each receive a wrong division. Name the part of the code responsible in each case, and state whether changing only the operator from > to >= would repair all three faults.
Read the worked answer
Candidate A. total = 99, so 99 // 3 = 33 and avg is exactly 33. The test avg > 33 is strict, so it is False; avg >= 45 is also False; control falls into else and prints "First". Two faults meet here - the wrong comparison operator, and an else that is being used as though it meant "First".
Candidate B. total = 132, avg = 44, so the first branch matches and "Third" is printed. The arithmetic and the band are both correct; the program simply never inspects the individual marks. The rule "at least 33 in every subject" is missing from the code entirely, so a candidate with 32 can never be caught however the average is computed.
Candidate C. total = 78, avg = 26. 26 > 33 is False, 26 >= 45 is False, so else fires and prints "First". "First" is the fall-through value, which is backwards - the top division should be the hardest to reach, not the default.
Does > → >= fix everything? No. It repairs A only. B needs a subject-wise check placed before the average is examined, and C needs the last branch to be an explicit avg >= 60 test with "Fail" as the final fall-through. All three defects must be fixed, and each needs its own test case.
2. Rewrite the grading logic so that it obeys the school rules and prints the division with the average to two decimal places. Justify each change you make and state the output for candidates A, B and C.
Read the worked answer
m1 = int(input())
m2 = int(input())
m3 = int(input())
total = m1 + m2 + m3
avg = total / 3
if m1 < 33 or m2 < 33 or m3 < 33 or avg < 33:
division = "Fail"
elif avg < 45:
division = "Third"
elif avg < 60:
division = "Second"
else:
division = "First"
print(division, f"{avg:.2f}")
Justification.
- The subject-wise test comes first because it overrides the average; a candidate with 32 in one subject fails whatever the average says.
orwith three< 33tests is equivalent to "not at least 33 in every subject". - The
avg < 33clause is folded into the sameifbecause a low average is also a Fail. - Ordered
elifchain: onceavg < 45is reached we already know avg >= 33 (it survived the first test) and >= 33 with< 45is exactly the Third band. This removes both the redundantandconditions and the dangerous catch-allelse. total / 3(true division) instead of//, so the value is not truncated before it is printed.f"{avg:.2f}"rounds to two decimals for display without changing the stored value.
Outputs. A → Third 33.00; B → Fail 44.00; C → Fail 26.00. Candidate D prints Third 44.67.
Boundary tests worth running: 33/33/33, 32/60/60, 45/45/45, 59/60/60, 60/60/60 and 0/0/0.
3. The clerk asks whether using `total // 3` for the average would actually change any division, once the subject-wise check is in place, as long as the cut-offs stay at 33, 45 and 60. Give a reasoned answer, including the case where the school later moves a cut-off to 44.5.
Read the worked answer
For non-negative totals, flooring does not by itself shift a boundary at an integer cut-off: total // 3 >= 45 exactly when total >= 135, which is exactly when the true average is >= 45. Same for 33 and 60. So with a correct >= test and integer cut-offs, candidate D's 134 marks still give Third whether you compute 44.67 or 44, and no division changes.
That is precisely why the bug is easy to miss. What integer division does break is everything around the grade:
- The band below 33. Combined with the strict
>, flooring creates a dead band: every average from 33.0 to 33.99 becomes 33, and33 > 33is False. Candidate A falls into that band. The>is the real culprit, but//widens the damage. - Reporting. D's average prints as 44 rather than 44.67, so a parent querying a mark 0.67 below Second cannot be answered from the printout.
- Future cut-offs. If the school sets a non-integer cut-off such as 44.5 for a scholarship,
total // 3on 134 gives 44, so a student with a true average of 44.67 is wrongly placed in Third instead of above 44.5. - Testing. Near-boundary cases look identical to clearly-inside cases, so defects hide during testing.
Recommendation: keep true division for display, and for exact comparison use integer arithmetic on the total, e.g. total >= 135 for Second and total >= 180 for First. That avoids both truncation and floating-point equality surprises such as 0.1 + 0.2 == 0.3 being False.