AI Computer Institute
Expert-curated CS & AI curriculum aligned to CBSE standards. A bharath.ai initiative. About Us

Debugging

📚 Technology⏱️ 21 min read🎓 Grade 8
✍️ AI Computer Institute Editorial Team Updated: August 2026 CBSE-aligned · Peer-reviewed · 21 min read
Content curated by subject matter experts with IIT/NIT backgrounds. All chapters are fact-checked against official CBSE/NCERT syllabi.

Imagine you write a program for your school's cafeteria counter. It is supposed to add up the price of every item a student picks and print the total. You test it once, buy a samosa and a juice box, and the screen correctly prints "Total: Rs 35". You hand it over to the counter staff. Twenty minutes later, a student buys four items and the screen prints "Total: Rs 0". Nothing crashed. No red error text appeared. The program is simply, quietly, wrong. This is the situation almost every programmer faces almost every day, and the skill of hunting down exactly why a program misbehaves — and fixing it without breaking anything else — is called debugging. It is not a side skill you pick up after learning "real" programming. For working programmers, debugging is a huge share of the actual job, often more time than writing the original code.

What Exactly Is a "Bug"?

A bug is any flaw in a program that makes it behave differently from what it was intended to do. The word predates computers — engineers used "bug" for mechanical glitches in machinery well before electronic computers existed. A famous, often-retold moment in computing history comes from September 1947, when a team working on the Harvard Mark II relay computer at Harvard University found an actual moth trapped in one of the machine's relays, causing a malfunction. A member of the team taped the moth into the operations logbook next to a note about the incident. Grace Hopper, a computer scientist who was also part of that research group, did not personally discover or tape in the moth, but she later told and popularized this story so widely that it became attached to her name in popular retellings. Whatever its exact authorship, the anecdote stuck, and "debugging" — the act of removing bugs — became the standard term for the process of finding and fixing errors in software.

The important idea is not the moth. It is this: a bug is not a mysterious curse. It is a specific, locatable, fixable mistake in a specific line or logical step of a program, put there by whoever wrote the code (including, very often, you, a few minutes ago). Debugging is the disciplined process of finding that exact spot.

Three Kinds of Bugs

Before you can fix a bug, it helps enormously to know which of three broad categories it belongs to, because each category is hunted down differently.

1. Syntax errors happen when your code breaks the grammar rules of the programming language, so the program cannot even start running. Python (the language used throughout this chapter, and common in CBSE Computer Science) refuses to run code like this:

print("Marks:" 78)

There is a missing comma between the string and the number, so Python's interpreter stops immediately with a message such as SyntaxError: invalid syntax, pointing at the exact line. Syntax errors are, in a strange way, the friendliest bugs: the computer tells you precisely where the problem is before your program ever runs, and you cannot ignore them even if you wanted to.

2. Runtime errors happen when your code is grammatically correct and starts running fine, but then it tries to do something the computer physically cannot do partway through, and the program crashes. For example:

marks = [78, 65, 90]
print(marks[5])

This is valid Python grammar. It runs, prints nothing yet, then crashes with IndexError: list index out of range, because the list marks only has three items, at positions 0, 1, and 2 — there is no position 5. Like syntax errors, runtime errors at least announce themselves loudly with an error message and a line number, giving you a strong hint about where to look.

3. Logical errors are the hardest kind, and the main reason debugging is treated as a genuine skill rather than just "reading the error message." A logical error means the code runs perfectly, start to finish, with no crash and no error message at all — but it computes the wrong answer, because the underlying logic (the plan, the algorithm, the conditions) is flawed. For example:

def is_even(n):
    return n % 2 == 1

Call is_even(4) and Python happily runs this function and returns False, when it should return True — because 4 % 2 is 0, not 1, so 0 == 1 is False. The function runs without complaint every single time; it is simply testing for "odd" while calling itself "is_even." No red text ever appears to warn you. This is why the cafeteria program from the opening example is so dangerous: it never crashed, so nobody suspected anything was wrong until a student noticed their total was Rs 0.

The Debugging Cycle

Experienced programmers do not hunt for bugs randomly by staring at the whole program and hoping the mistake jumps out. They follow a repeatable five-step cycle. The diagram below shows the cycle; each stage feeds into the next, and after "Verify" you either close the bug or discover it was only partly fixed and loop back to "Reproduce" with better information.

THE DEBUGGING CYCLE 1. Reproduce trigger it on demand 2. Isolate narrow the location 3. Hypothesize guess & test the cause 4. Fix change one thing 5. Verify re-run & confirm

Notice the dashed arrow looping back from stage 5 to stage 1 — that loop is the whole point. Debugging is rarely a single pass. You reproduce the bug reliably, isolate the smallest piece of code responsible, form a hypothesis about the exact cause, make one small fix, and then verify with the original test case (and a few extra ones) before declaring victory. If verification fails, you loop back and reproduce again with what you just learned.

Worked Example 1: The Off-By-One Average

Here is a function meant to compute the average of a student's marks across four subjects:

def average(marks):
    total = 0
    for i in range(1, len(marks)):
        total += marks[i]
    return total / len(marks)

marks = [78, 65, 90, 82]
print(average(marks))

Run this and Python prints 59.25. The correct average of 78, 65, 90, and 82 is (78 + 65 + 90 + 82) / 4 = 315 / 4 = 78.75. The program ran with no crash and no error message — this is a pure logical error, and it will not announce itself. Let's apply the debugging cycle by hand-tracing exactly what the loop does.

The list has four elements, at index positions 0, 1, 2, and 3: marks[0] is 78, marks[1] is 65, marks[2] is 90, and marks[3] is 82. The buggy line is range(1, len(marks)), which is range(1, 4). In Python, range(1, 4) produces the sequence 1, 2, 3 — it starts at 1, not 0, and stops just before 4. Tracing the loop variable i through every iteration:

  1. Before the loop starts: total is 0.
  2. i = 1: the loop adds marks[1], which is 65. Now total is 0 + 65 = 65.
  3. i = 2: the loop adds marks[2], which is 90. Now total is 65 + 90 = 155.
  4. i = 3: the loop adds marks[3], which is 82. Now total is 155 + 82 = 237.
  5. The range is exhausted (4 is not included), so the loop ends with total equal to 237.
  6. The function returns total / len(marks), which is 237 / 4 = 59.25.

The trace reveals the bug precisely: marks[0], the value 78, was never added, because the loop's range started counting from 1 instead of 0. This is called an off-by-one error, one of the single most common bug categories in all of programming — it happens whenever a loop's boundary is shifted by exactly one position from where it should be. The fix is to start the range at 0, which in Python you can write simply as range(len(marks)):

def average(marks):
    total = 0
    for i in range(len(marks)):
        total += marks[i]
    return total / len(marks)

Re-tracing with this fix: i now takes the values 0, 1, 2, 3, so total becomes 78, then 78+65=143, then 143+90=233, then 233+82=315. The function returns 315 / 4 = 78.75, which matches the hand-calculated correct answer. That is the "Verify" stage of the cycle: we don't just trust that the fix looks right, we re-run the trace and confirm the number against an independently calculated expected value.

Worked Example 2: The Ticket Counter That Cheats Senior Citizens

Here is a function meant to give discounted movie tickets: a 40% discount for senior citizens aged 60 and above, and a smaller 20% discount for anyone aged 45 and above who is not yet a senior citizen.

def ticket_price(age):
    base = 1000
    if age >= 45:
        return base - 200
    elif age >= 60:
        return base - 400
    else:
        return base

print(ticket_price(65))

A 65-year-old customer should clearly qualify for the bigger senior-citizen discount and pay Rs 600. Instead, running this prints 800. No crash, no error — just a wrong, and in this case unfair, result. Let's trace it. Python evaluates if/elif conditions strictly in the order they are written, from top to bottom, and stops at the very first one that is True, ignoring every condition written after it, even if a later condition would also have matched. With age equal to 65:

  1. Python checks the first condition, age >= 45. Since 65 >= 45 is True, Python immediately executes return base - 200 and exits the function right there.
  2. The second condition, age >= 60 — the one that actually should have matched a 65-year-old for the bigger discount — is never even checked, because the function already returned.
  3. The returned value is 1000 − 200 = 800. Since base and the discount are both plain whole numbers (integers) in this code, Python's subtraction here stays a whole number too — there is no decimal point, unlike the average() function earlier, which used division (/) and therefore always produces a value with a decimal point, such as 78.75.

The bug is an ordering error in the conditions: the broader, less-specific condition (age >= 45, which also happens to be true for every senior citizen, since every senior citizen is also 45 or older) was placed before the narrower, more specific condition (age >= 60). Whenever you stack if/elif checks that overlap like this, the more specific, harder-to-satisfy condition must always be checked first, or it will never be reached. The fix simply swaps the order:

def ticket_price(age):
    base = 1000
    if age >= 60:
        return base - 400
    elif age >= 45:
        return base - 200
    else:
        return base

print(ticket_price(65))

Tracing again with age equal to 65: Python checks age >= 60 first. Since 65 >= 60 is True, it immediately returns base - 400, which is 1000 − 400 = 600. The 45-and-above branch is never even reached for this customer, which is exactly correct, because a 65-year-old should get the senior discount, not the smaller one. Verifying with a second test case, a 50-year-old: age >= 60 is False (50 is less than 60), so Python moves to elif age >= 45, which is True, returning 1000 − 200 = 800. Both the senior and the middle-age branches now behave as intended.

Debugging Techniques That Actually Work

Hand-tracing on paper, as done above, is the most reliable technique there is, but it becomes slow for long programs. Real programmers combine it with a few practical tools:

Print-statement tracing. Temporarily insert print() statements at key points to reveal what a variable actually contains while the program runs, rather than what you assume it contains. In the average-marks example, adding print(i, marks[i], total) inside the loop would have immediately shown that i started at 1 instead of 0, without needing to trace by hand at all.

Binary-search bug hunting. When a bug is somewhere inside a long program and you don't know which section is responsible, don't read the code from the top line by line. Instead, place a print statement (or a temporary check) roughly in the middle of the program's execution. If the values look correct up to that point, the bug is in the second half; if they already look wrong by then, the bug is in the first half. Repeat this halving inside whichever half is guilty. This is the same "eliminate half the possibilities each time" idea used in binary search over a sorted list, applied to hunting through a program's execution instead of through data — and it finds the guilty section in a handful of steps even in a program hundreds of lines long.

Rubber-duck debugging. Explain your code, line by line, out loud, to another person, or even to an inanimate object like a rubber duck on your desk, as if they know nothing about the program. The act of forcing your own assumptions into precise spoken sentences ("this loop starts at i equals 1 because...") very often makes a wrong assumption embarrassingly obvious to you mid-sentence, before the listener says a single word back.

Using a debugger with breakpoints. Most code editors (including free ones you likely already have, such as VS Code, Thonny, or the debugger built into many online Python environments) let you set a breakpoint on a specific line. When you run the program in debug mode, execution pauses exactly at that line, and you can inspect the live value of every variable at that exact moment, then step forward one line at a time, watching values change. This is strictly more powerful than scattering print() statements, because you don't have to predict in advance which variable you'll need to inspect — you can look at anything, at any paused moment, and then remove all the temporary breakpoints afterward without editing your actual code.

A Common Misconception

Many students believe that if a program runs and prints something — anything — without a red error message, then the code must be "working." Worked Example 1 and Worked Example 2 both directly disprove this: average([78, 65, 90, 82]) ran cleanly and printed a confident-looking number, 59.25, and ticket_price(65) ran cleanly and printed 800. Both are completely wrong answers. The absence of a crash only tells you that the code obeyed Python's grammar and never asked the computer to do something physically impossible (like reading a list position that doesn't exist). It tells you nothing at all about whether the code's underlying logic matches what you actually intended it to compute. The only real defense against logical errors is to independently calculate the expected answer for at least one test case by hand (as we did: 315 / 4 = 78.75, and a 65-year-old should clearly get the bigger discount) and compare it against what the program actually printed, every single time you write or change a function — not just when the program crashes.

Check Your Understanding

  • Classify each of the following as a syntax error, a runtime error, or a logical error, and justify your choice: (a) total = marks[0] + marks[1] + marks[2] + marks[3] is used to compute the sum of a five-item list, silently ignoring the fifth mark. (b) print("Score is" score) is missing a comma before score. (c) A list has 3 items and the code accesses my_list[3].
  • A function def is_leap_year(year): return year % 4 == 0 is meant to check leap years, but incorrectly reports 1900 as a leap year (it was not, because centuries not divisible by 400 are excluded from leap years). Is this a syntax, runtime, or logical error? What is the first debugging-cycle step you would take?
  • Trace ticket_price(45) by hand through the corrected version from Worked Example 2 (checking age >= 60 before age >= 45). Write out which condition is checked first, whether it is True or False, and the final returned value.
  • Explain, in your own words, why binary-search bug hunting is faster than reading a 300-line program from the very first line downward.
  • Why is "the program didn't crash" not sufficient evidence that a program is correct? Give an example from this chapter, and describe one test you could add to catch a similar mistake.

Summary

A bug is a specific, locatable mistake that makes a program behave differently from what was intended, and debugging is the disciplined process of finding and correcting it. Bugs fall into three categories: syntax errors (broken grammar, caught before the program runs), runtime errors (the program crashes partway through because it attempted something impossible, such as accessing a list position that doesn't exist), and logical errors (the program runs fine and prints a confident-looking but wrong answer, with no error message at all, making these the hardest to catch). Experienced programmers do not search randomly; they follow a repeatable cycle — reproduce the bug reliably, isolate the smallest section of code responsible, form a specific hypothesis about the cause, make one targeted fix, and verify the fix against an independently calculated expected answer, looping back if verification fails. Two named bug patterns recur constantly in real code: off-by-one errors, where a loop's start or stop boundary is shifted by exactly one position (as when range(1, len(marks)) skips the first element), and condition-ordering errors, where a broader if/elif condition placed before a narrower one silently swallows cases that should have reached the narrower, more specific branch. Practical techniques for finding bugs include hand-tracing variable values step by step, inserting temporary print statements, using an editor's breakpoint-based debugger to pause execution and inspect live variable values, explaining your code aloud to catch your own wrong assumptions, and halving the search space repeatedly (binary-search style) to locate which section of a long program is at fault. The single most important habit is to never trust a program simply because it ran without crashing — always compare its output against an answer you calculated independently by hand.

Practice Exercises

Now it is time to practice! Complete these challenges to solidify your understanding:

  • Exercise 1: Write a short program that demonstrates the core concept from this chapter. Test it with at least 3 different inputs.
  • Exercise 2: Find a real-world example where debugging is used in an Indian company (like TCS, Infosys, Flipkart, or ISRO). Write a paragraph explaining the connection.
  • Exercise 3: Create a mind-map connecting debugging to at least 3 other topics you have studied.
← Space ComplexityCode Review →

Found this useful? Share it!

📱 WhatsApp 🐦 Twitter 💼 LinkedIn