EASYTUTORGUIDE

Practical tutorials, tools, courses, digital skills, and business promotion.

Free Learning

JavaScript – Chapter 2: Development Environment

Learn what JavaScript is, where it runs, why it is important, and how beginners can start using it with simple examples and clear output.

Beginner Friendly JavaScript Basics Web Development Code Examples
JavaScript Lesson 2 Chapter 2 Topics Day / Night Mode

Main reading content

python-course-chapter-34

Chapter 34: Debugging Python Programs

A complete beginner-friendly guide to finding, understanding, and correcting syntax errors, runtime errors, logic mistakes, exceptions, and unexpected program behavior.

Goal: Learn a clear debugging process using error messages, tracebacks, print statements, breakpoints, pdb, VS Code, variable inspection, call stacks, and practical problem-solving strategies.

Chapter 34 Topics

``` ```

34.1 Understanding Bugs

```

A bug is a problem in a program that causes incorrect behavior, an error message, unexpected output, or a complete program failure. Bugs may be caused by typing mistakes, incorrect assumptions, invalid data, missing conditions, or misunderstood requirements.

Debugging is the process of locating the cause of a problem, understanding why it happens, correcting it, and checking that the correction does not create another problem. Debugging is a normal part of programming, even for experienced developers.

Buggy Example

price = 20
```

quantity = 3

# The programmer intended to calculate 13% tax.

tax = price * quantity * 13

total = price * quantity + tax

print("Total:", total)
```

Incorrect Output

Total: 840

Corrected Example

price = 20
```

quantity = 3

subtotal = price * quantity

# Use 0.13 to represent 13 percent.

tax = subtotal * 0.13

total = subtotal + tax

print("Subtotal:", subtotal)
print("Tax:", tax)
print("Total:", total)
```

Correct Output

Subtotal: 60
```

Tax: 7.8
Total: 67.8
```

Explanation

The program ran without displaying an exception, but the formula used 13 instead of 0.13. This is a logic bug because Python successfully followed the instructions, but the instructions were incorrect.

```

34.2 Syntax Bugs

```

A syntax bug happens when code does not follow Python's grammar rules. Common causes include missing colons, unmatched parentheses, missing quotation marks, incorrect indentation, and misspelled keywords.

Python normally detects syntax problems before running the program. The error message usually points near the place where Python became unable to understand the code, although the real mistake may appear slightly earlier.

Buggy Example

name = "Sara"
```

if name == "Sara"
print("Welcome, Sara")
```

Error

SyntaxError: expected ':'

Corrected Example

name = "Sara"
```

if name == "Sara":
print("Welcome, Sara")
```

Output

Welcome, Sara

Another Syntax Bug

message = "Learning Python
```

print(message)
```

Error

SyntaxError: unterminated string literal

Corrected Version

message = "Learning Python"
```

print(message)
```

Output

Learning Python
```

34.3 Runtime Bugs

```

A runtime bug occurs after Python successfully understands the program and begins executing it. The program may work for some values but fail when it encounters invalid input or an unexpected condition.

Common runtime errors include division by zero, missing files, invalid list indexes, incorrect type operations, and attempts to use names that do not exist.

Buggy Example

total = 100
```

number_of_students = 0

average = total / number_of_students

print(average)
```

Error

ZeroDivisionError: division by zero

Corrected Example

total = 100
```

number_of_students = 0

if number_of_students == 0:
print("The average cannot be calculated.")
else:
average = total / number_of_students
print("Average:", average)
```

Output

The average cannot be calculated.

Another Runtime Error

names = ["Sara", "Michael"]
```

print(names[5])
```

Error

IndexError: list index out of range
```

34.4 Logic Bugs

```

A logic bug occurs when a program runs without an exception but produces the wrong result. These bugs can be more difficult to find because Python may not display any error message.

Logic bugs often involve incorrect formulas, wrong comparison operators, incorrect loop boundaries, misplaced indentation, or conditions written in the wrong order.

Buggy Example

score = 95
```

if score >= 60:
grade = "D"
elif score >= 70:
grade = "C"
elif score >= 80:
grade = "B"
elif score >= 90:
grade = "A"
else:
grade = "F"

print("Grade:", grade)
```

Incorrect Output

Grade: D

Corrected Example

score = 95
```

# Check the highest range first.

if score >= 90:
grade = "A"
elif score >= 80:
grade = "B"
elif score >= 70:
grade = "C"
elif score >= 60:
grade = "D"
else:
grade = "F"

print("Grade:", grade)
```

Correct Output

Grade: A

Explanation

In the buggy version, 95 satisfies the first condition because it is greater than 60. Python does not check later branches after finding a true condition.

```

34.5 Reading Error Messages

```

Python error messages usually identify the exception type and provide a short description. The exception type tells you the general category of the problem, while the description gives more specific information.

Beginners should read the final line first. Then examine the filename, line number, and highlighted expression. Avoid changing unrelated code before understanding what the message is reporting.

Example

price = "20"
```

quantity = 3

total = price + quantity

print(total)
```

Error Message

Traceback (most recent call last):
```

File "shop.py", line 4, in 
total = price + quantity
TypeError: can only concatenate str (not "int") to str
```

How to Read It

  • shop.py is the filename.
  • Line 4 is where the failure occurred.
  • TypeError is the exception category.
  • The program tried to combine a string and an integer.

Corrected Example

price = 20
```

quantity = 3

total = price + quantity

print(total)
```

Output

23

Common Error Types

  • SyntaxError: Python cannot understand the code structure.
  • NameError: A name has not been defined.
  • TypeError: An operation received an unsuitable type.
  • ValueError: The type is acceptable, but the value is invalid.
  • IndexError: A sequence index does not exist.
  • KeyError: A dictionary key does not exist.
  • AttributeError: An object does not provide the requested attribute.
  • FileNotFoundError: The requested file could not be found.
```

34.6 Reading Tracebacks

```

A traceback shows the sequence of function calls that led to an exception. Each entry normally includes a filename, line number, function name, and source-code line.

Read the final line to identify the exception. Then move upward through the traceback to understand which function called which other function. The lowest entry from your own code is often the immediate failure location.

Example

def calculate_average(total, count):
return total / count
```

def create_report(scores):
total = sum(scores)
count = len(scores)

```
return calculate_average(total, count)
```

def main():
scores = []

```
print(create_report(scores))
```

main()
```

Traceback

Traceback (most recent call last):
```

File "report.py", line 20, in 
main()
File "report.py", line 17, in main
print(create_report(scores))
File "report.py", line 9, in create_report
return calculate_average(total, count)
File "report.py", line 2, in calculate_average
return total / count
ZeroDivisionError: division by zero
```

Call Order

  1. The module called main().
  2. main() called create_report().
  3. create_report() called calculate_average().
  4. calculate_average() attempted division by zero.

Corrected Function

def calculate_average(total, count):
if count == 0:
    return 0

return total / count
```

34.7 Debugging with print()

```

Print debugging means temporarily displaying variable values, program locations, conditions, and function arguments. It is one of the simplest ways to understand what a program is doing.

Debug messages should be descriptive. Instead of printing only a number, print the variable name and value. Remove unnecessary debugging output after the problem is corrected, or replace it with proper logging.

Buggy Program

prices = [10, 20, 30]
```

total = 0

for price in prices:
total = price

print("Total:", total)
```

Incorrect Output

Total: 30

Add Debugging Prints

prices = [10, 20, 30]
```

total = 0

print("Starting total:", total)

for price in prices:
print("Current price:", price)
print("Total before assignment:", total)

```
total = price

print("Total after assignment:", total)
print("-" * 30)
```

print("Final total:", total)
```

Debug Output

Starting total: 0
```

Current price: 10
Total before assignment: 0
Total after assignment: 10
--------------------------

Current price: 20
Total before assignment: 10
Total after assignment: 20
--------------------------

Current price: 30
Total before assignment: 20
Total after assignment: 30
--------------------------

Final total: 30
```

Corrected Program

prices = [10, 20, 30]
```

total = 0

for price in prices:
total += price

print("Total:", total)
```

Correct Output

Total: 60
```

34.8 Using breakpoint()

```

The built-in breakpoint() function pauses a running Python program and opens the configured debugger. By default, it normally starts the Python debugger.

While paused, you can inspect variables, execute expressions, move through the program, and continue execution. This avoids adding many temporary print statements.

Example

def calculate_discount(
price,
discount_rate
```

):
discount = price * discount_rate

```
# Pause the program here.
breakpoint()

final_price = price - discount

return final_price
```

result = calculate_discount(
100,
0.20
)

print("Final price:", result)
```

Debugger Session

> example.py(10)calculate_discount()
```

-> final_price = price - discount
(Pdb) price
100
(Pdb) discount_rate
0.2
(Pdb) discount
20.0
(Pdb) continue
Final price: 80.0
```

Explanation

The program pauses before calculating the final price. Typing variable names displays their current values. The continue command resumes normal execution.

```

34.9 The Python Debugger

```

A debugger is a tool that controls a program while it runs. It can pause execution, inspect variables, execute one line at a time, enter functions, leave functions, and display the call stack.

Debuggers are especially useful when the program contains several functions or when temporary print statements would create too much output.

Common Debugger Actions

  • Pause at a selected line.
  • Continue until the next breakpoint.
  • Step over the current line.
  • Step into a function call.
  • Step out of the current function.
  • Inspect local and global variables.
  • Evaluate expressions.
  • View the chain of active function calls.

Example Program

def apply_tax(
subtotal,
tax_rate
```

):
tax = subtotal * tax_rate
return subtotal + tax

def calculate_order(
price,
quantity
):
subtotal = price * quantity
total = apply_tax(
subtotal,
0.13
)

```
return total
```

print(calculate_order(25, 3))
```

Output

84.75

A debugger can pause inside calculate_order(), inspect subtotal, step into apply_tax(), and then inspect the calculated tax.

```

34.10 pdb

```

The pdb module is Python's built-in command-line debugger. It can run scripts, pause at breakpoints, inspect values, and step through code.

It is useful when a graphical debugger is unavailable, when working in a terminal, or when debugging small scripts quickly.

Run a Program with pdb

python -m pdb program.py

Example Program

def divide(first, second):
result = first / second
return result
```

answer = divide(20, 4)

print(answer)
```

Useful pdb Commands

  • n: Execute the next line.
  • s: Step into a function.
  • r: Continue until the current function returns.
  • c: Continue execution.
  • p expression: Print an expression.
  • pp expression: Pretty-print an expression.
  • l: List nearby source code.
  • w: Display the call stack.
  • u: Move up the call stack.
  • d: Move down the call stack.
  • b line_number: Set a breakpoint.
  • cl: Clear breakpoints.
  • q: Quit the debugger.

Example Session

(Pdb) break 2
```

Breakpoint 1 at program.py:2
(Pdb) continue
> program.py(2)divide()
-> result = first / second
(Pdb) p first
20
(Pdb) p second
4
(Pdb) next
(Pdb) p result
5.0
(Pdb) continue
5.0

34.11 VS Code Debugger

```

Visual Studio Code includes a graphical debugging interface for Python when the Python extension is installed. It allows breakpoints to be added by clicking beside line numbers.

During debugging, VS Code can display local variables, watched expressions, active breakpoints, the call stack, and a debug console.

Basic Steps

  1. Open the Python file in VS Code.
  2. Install or enable the Python extension.
  3. Select the correct Python interpreter.
  4. Click beside a line number to create a breakpoint.
  5. Open the Run and Debug panel.
  6. Select Python File as the debug configuration.
  7. Start debugging.
  8. Inspect variables after execution pauses.
  9. Use Step Over, Step Into, Step Out, or Continue.

Example Program

def calculate_total(
prices
```

):
total = 0

```
for price in prices:
    total += price

return total
```

products = [
19.99,
25.50,
10.00
]

result = calculate_total(products)

print("Total:", result)
```

Output

Total: 55.49

Place a breakpoint on total += price. Each time the loop pauses, inspect price and total to watch the calculation change.

```

34.12 Breakpoints

```

A breakpoint tells the debugger to pause before executing a selected line. It allows you to examine the program at an exact location.

Good breakpoint locations include the start of a suspicious function, before a failing calculation, inside an important loop, or immediately before data changes.

Example

def calculate_average(scores):
total = sum(scores)
count = len(scores)
average = total / count

return average
```

scores = [80, 90, 100]

print(calculate_average(scores))
```

Suggested Breakpoints

  • On total = sum(scores) to inspect the input list.
  • On average = total / count to inspect both values.
  • On return average to verify the result.

Output

90.0

Breakpoint Advice

Avoid placing breakpoints on every line. Begin near the suspected problem and move the breakpoint as you learn more about the program.

```

34.13 Conditional Breakpoints

```

A conditional breakpoint pauses only when a specified expression becomes true. It is useful when a loop runs many times but the problem occurs only for one record or value.

Instead of repeatedly pressing Continue, you can set a condition such as score < 0, index == 500, or student["name"] == "Sara".

Example

scores = [
80,
92,
75,
-5,
88
```

]

for index, score in enumerate(scores):
print(
index,
score
)
```

Conditional Breakpoint

score < 0

Place the breakpoint on the print() line and use the condition above. The debugger pauses only when score is negative.

Output

0 80
```

1 92
2 75
3 -5
4 88
```

Useful Conditions

  • index == 100
  • total > 1000
  • name == "Michael"
  • item is None
  • len(items) == 0
```

34.14 Stepping Through Code

```

Stepping lets you execute a paused program in small parts. It helps reveal the exact line where a value becomes incorrect.

Step Over executes the current line without entering called functions. Step Into enters a called function. Step Out completes the current function and returns to its caller.

Example

def apply_discount(
price,
rate
```

):
discount = price * rate
return price - discount

def create_total(
price,
quantity
):
subtotal = price * quantity
total = apply_discount(
subtotal,
0.10
)

```
return total
```

print(create_total(20, 3))
```

Output

54.0

How to Step Through It

  1. Pause at subtotal = price * quantity.
  2. Use Step Over to calculate the subtotal.
  3. Inspect subtotal.
  4. Use Step Into on the apply_discount() call.
  5. Inspect price, rate, and discount.
  6. Use Step Out to return to create_total().
  7. Inspect the final total.
```

34.15 Inspecting Variables

```

Inspecting variables means examining their current values and types while a program is paused. You can inspect simple values, lists, dictionaries, objects, function arguments, and nested data.

Do not inspect only the variable that fails. Also inspect the values used to create it. The problem may have started earlier in the program.

Example

order = {
"price": "25.00",
"quantity": 3,
"tax_rate": 0.13
```

}

subtotal = (
order["price"]
* order["quantity"]
)

print(subtotal)
```

Unexpected Output

25.0025.0025.00

Variables to Inspect

order
```

order["price"]
type(order["price"])
order["quantity"]
type(order["quantity"])
```

Corrected Example

order = {
"price": "25.00",
"quantity": 3,
"tax_rate": 0.13
```

}

price = float(
order["price"]
)

subtotal = (
price
* order["quantity"]
)

print(subtotal)
```

Correct Output

75.0
```

34.16 Call Stacks

```

The call stack records the active chain of function calls. Each active function has a stack frame containing its local variables, arguments, and current execution position.

When one function calls another, a new frame is added. When the called function returns, its frame is removed. Debuggers allow you to move between frames and inspect variables from different levels.

Example

def calculate_tax(
subtotal
```

):
tax_rate = 0.13
return subtotal * tax_rate

def calculate_total(
subtotal
):
tax = calculate_tax(subtotal)
return subtotal + tax

def process_order(
price,
quantity
):
subtotal = price * quantity
return calculate_total(subtotal)

print(process_order(20, 3))
```

Output

67.8

Call Stack While Inside calculate_tax()

<module>
```

process_order()
calculate_total()
calculate_tax()
```

Variables by Frame

  • process_order(): price, quantity, and subtotal
  • calculate_total(): subtotal and tax
  • calculate_tax(): subtotal and tax_rate
```

34.17 Debugging Exceptions

```

Exception handling can prevent a program from stopping, but overly broad exception handling can hide useful debugging information. Catch only exceptions you can handle meaningfully.

While debugging, examine the exception type, message, input values, and traceback. Avoid using an empty except block because it hides the problem.

Poor Exception Handling

try:
number = int("Python")
```

except:
pass

print("Program finished.")
```

Output

Program finished.

The problem is hidden completely. A developer receives no useful information.

Better Exception Handling

value = "Python"
```

try:
number = int(value)

except ValueError as error:
print(
"Could not convert value:",
value
)

```
print(
    "Error:",
    error
)

Output

Could not convert value: Python
```

Error: invalid literal for int() with base 10: 'Python'
```

Preserving the Original Exception

def parse_age(value):
try:
    return int(value)

except ValueError as error:
    raise ValueError(
        f"Invalid age value: {value}"
    ) from error
```

parse_age("unknown")
```

Result

The new exception explains the program-specific problem while preserving the original conversion error as its cause.

```

34.18 Remote Debugging Concepts

```

Remote debugging means controlling a program running in another process, computer, virtual machine, container, or server from a local debugging interface.

A debugging service usually listens for a debugger connection or connects back to the development environment. Because a debugger may provide extensive program access, remote debugging must be secured and should not be exposed publicly.

Common Remote Debugging Situations

  • A Python application runs inside a development container.
  • A web application runs on another development computer.
  • A background worker runs in a separate process.
  • A program behaves differently on a testing server.
  • A cloud development environment hosts the application.

Conceptual Flow

Local editor
|
| Secure debugger connection
|
```

Remote Python process
|
| Breakpoints, variables, stack frames
|
Running application
```

Important Safety Practices

  • Use remote debugging only in controlled development environments.
  • Do not expose debugger ports directly to the public internet.
  • Use authentication, secure networking, or trusted tunnels.
  • Remove debugging configuration from production deployments.
  • Avoid displaying passwords, tokens, and private customer data.
```

34.19 Rubber Duck Debugging

```

Rubber duck debugging is a method where you explain your code line by line to another person, an object, or yourself. The listener does not need to understand programming.

Explaining the code forces you to state what each line should do, what values should exist, and why each decision is correct. The difference between what you say and what the code actually does often reveals the bug.

Example

numbers = [1, 2, 3, 4]
```

total = 0

for number in numbers:
total =+ number

print(total)
```

Incorrect Output

4

Explain It Line by Line

  1. Create a list containing four numbers.
  2. Start the total at zero.
  3. Visit each number.
  4. Add the number to the existing total.
  5. Display the completed total.

While explaining step four, you may notice that =+ assigns a positive value. It does not mean add and assign. The intended operator is +=.

Corrected Program

numbers = [1, 2, 3, 4]
```

total = 0

for number in numbers:
total += number

print(total)
```

Correct Output

10
```

34.20 Debugging Strategies

```

Effective debugging uses a repeatable process instead of random code changes. Begin by reproducing the problem consistently and recording the exact input, output, and error information.

Reduce the problem to the smallest failing example, form one hypothesis, test it, and observe the result. Change one important thing at a time so you know which correction affected the behavior.

Recommended Process

  1. Reproduce the problem.
  2. Read the complete error message.
  3. Identify the smallest failing input.
  4. Determine the expected result.
  5. Inspect values near the failure.
  6. Check assumptions about types and ranges.
  7. Add a breakpoint or focused debug output.
  8. Form one clear explanation for the failure.
  9. Test that explanation.
  10. Apply the smallest appropriate correction.
  11. Run the original failing case again.
  12. Test normal, empty, minimum, maximum, and invalid inputs.
  13. Remove temporary debugging code.
  14. Add a test that prevents the bug from returning.

Binary Search Debugging

In a large program, place a check around the middle of the execution path. Determine whether the values are already incorrect at that point. Continue narrowing the suspicious area until the exact line or function is identified.

Useful Questions

  • What did I expect?
  • What actually happened?
  • What is the smallest input that reproduces it?
  • Which value first becomes incorrect?
  • What assumption did the code make?
  • Is the data type correct?
  • Could the collection be empty?
  • Could a value be None?
  • Is the condition order correct?
  • Does the loop include every intended item?
```

34.21 Chapter Practice Exercises

```

The following exercises contain common debugging situations. For each exercise, identify the bug category, explain the cause, correct the code, and test the corrected version with more than one input.

  1. Correct a missing colon after an if statement.
  2. Correct an unterminated string.
  3. Correct inconsistent indentation.
  4. Correct a misspelled variable name causing NameError.
  5. Prevent division by zero.
  6. Prevent access to an invalid list index.
  7. Handle a missing dictionary key.
  8. Convert user input before arithmetic.
  9. Correct an incorrect tax formula.
  10. Correct an incorrectly ordered grade condition.
  11. Correct an off-by-one loop error.
  12. Use print statements to trace a changing total.
  13. Use breakpoint() inside a function.
  14. Run a script with pdb.
  15. Use next and step in pdb.
  16. Set a graphical breakpoint in VS Code.
  17. Create a conditional breakpoint for a negative value.
  18. Inspect a list and its current loop item.
  19. Inspect a dictionary containing an incorrect data type.
  20. Move between frames in a call stack.
  21. Catch a specific exception instead of every exception.
  22. Preserve an original exception with raise ... from.
  23. Use rubber duck debugging on a faulty loop.
  24. Reduce a large failing program to a small example.
  25. Write down expected and actual values.
  26. Test a function with an empty list.
  27. Test a function with minimum and maximum values.
  28. Add an assertion that checks an important assumption.
  29. Replace temporary debugging prints with logging.
  30. Create a regression test for a corrected bug.
  31. Debug a student average calculator.
  32. Debug a shopping-cart total calculator.
  33. Debug a password validator.
  34. Debug a file-reading program.
  35. Debug a nested function traceback.
  36. Debug a loop that skips the final item.
  37. Debug a function returning the wrong type.
  38. Debug a program that modifies the wrong variable.
  39. Debug a dictionary lookup with unexpected capitalization.
  40. Build a complete debugging report for a faulty program.

Practice Example

def calculate_average(scores):
total = 0

for score in scores:
    total = score

return total / len(scores)
```

student_scores = [
80,
90,
100
]

print(
calculate_average(
student_scores
)
)
```

Incorrect Output

33.333333333333336

Problems

  • The loop replaces the total instead of adding to it.
  • An empty list would cause division by zero.

Corrected Version

def calculate_average(scores):
if not scores:
    return 0.0

total = 0

for score in scores:
    total += score

return total / len(scores)
```

student_scores = [
80,
90,
100
]

print(
calculate_average(
student_scores
)
)

print(
calculate_average([])
)
```

Correct Output

90.0
```

0.0

34.22 Chapter Debugging Project

```

This project provides a faulty shopping-cart program containing syntax, runtime, and logic problems. The goal is to debug the application systematically instead of immediately replacing the entire program.

Faulty Shopping-Cart Program

products = {
"pizza": 14.99,
"burger": 9.99,
"drink": 2.50
```

}

cart = [
{
"name": "pizza",
"quantity": 2
},
{
"name": "burger",
"quantity": "3"
},
{
"name": "salad",
"quantity": 1
}
]

def calculate_subtotal(cart, products)
subtotal = 0

```
for item in cart:
    name = item["name"]
    quantity = item["quantity"]
    price = products[name]

    item_total = price + quantity
    subtotal = item_total

return subtotal
```

def calculate_tax(subtotal):
return subtotal * 13

def calculate_delivery(subtotal):
if subtotal > 40:
return 5

```
return 0
```

subtotal = calculate_subtotal(
cart,
products
)

tax = calculate_tax(subtotal)
delivery = calculate_delivery(subtotal)

total = subtotal + tax + delivery

print("Subtotal:", subtotal)
print("Tax:", tax)
print("Delivery:", delivery)
print("Total:", total)
```

Problems to Find

  • The function definition is missing a colon.
  • One quantity is stored as a string.
  • The cart contains a product missing from the product dictionary.
  • Item totals use addition instead of multiplication.
  • The subtotal is replaced rather than accumulated.
  • The tax formula uses 13 instead of 0.13.
  • The delivery rule may be reversed from the intended requirement.
  • Money values are not rounded for display.

Debugging Version with Focused Output

products = {
"pizza": 14.99,
"burger": 9.99,
"drink": 2.50
```

}

cart = [
{
"name": "pizza",
"quantity": 2
},
{
"name": "burger",
"quantity": "3"
},
{
"name": "salad",
"quantity": 1
}
]

def calculate_subtotal(
cart,
products
):
subtotal = 0

```
for index, item in enumerate(cart):
    print()
    print("Processing index:", index)
    print("Item:", item)

    name = item["name"]
    quantity = item["quantity"]

    print("Name:", name)
    print("Quantity:", quantity)
    print(
        "Quantity type:",
        type(quantity)
    )

    if name not in products:
        print(
            "Unknown product:",
            name
        )
        continue

    try:
        quantity = int(quantity)

    except ValueError:
        print(
            "Invalid quantity:",
            quantity
        )
        continue

    price = products[name]

    item_total = (
        price
        * quantity
    )

    print("Price:", price)
    print(
        "Item total:",
        item_total
    )

    subtotal += item_total

    print(
        "Running subtotal:",
        subtotal
    )

return subtotal
```

subtotal = calculate_subtotal(
cart,
products
)

print()
print("Debug subtotal:", subtotal)
```

Debug Output

Processing index: 0
```

Item: {'name': 'pizza', 'quantity': 2}
Name: pizza
Quantity: 2
Quantity type: 
Price: 14.99
Item total: 29.98
Running subtotal: 29.98

Processing index: 1
Item: {'name': 'burger', 'quantity': '3'}
Name: burger
Quantity: 3
Quantity type: 
Price: 9.99
Item total: 29.97
Running subtotal: 59.95

Processing index: 2
Item: {'name': 'salad', 'quantity': 1}
Name: salad
Quantity: 1
Quantity type: 
Unknown product: salad

Debug subtotal: 59.95
```

Fully Corrected Shopping-Cart Program

from dataclasses import dataclass
```

from decimal import (
Decimal,
InvalidOperation,
ROUND_HALF_UP
)

MONEY_UNIT = Decimal("0.01")
TAX_RATE = Decimal("0.13")
FREE_DELIVERY_MINIMUM = Decimal("40.00")
DELIVERY_CHARGE = Decimal("5.00")

@dataclass
class CartItem:
name: str
quantity: int

PRODUCTS: dict[str, Decimal] = {
"pizza": Decimal("14.99"),
"burger": Decimal("9.99"),
"drink": Decimal("2.50")
}

RAW_CART = [
{
"name": "pizza",
"quantity": 2
},
{
"name": "burger",
"quantity": "3"
},
{
"name": "salad",
"quantity": 1
}
]

def round_money(
amount: Decimal
) -> Decimal:
return amount.quantize(
MONEY_UNIT,
rounding=ROUND_HALF_UP
)

def parse_quantity(
value: object
) -> int:
try:
quantity = int(value)

```
except (
    TypeError,
    ValueError
) as error:
    raise ValueError(
        f"Invalid quantity: {value}"
    ) from error

if quantity <= 0:
    raise ValueError(
        "Quantity must be greater than zero."
    )

return quantity
```

def build_cart(
raw_cart: list[dict[str, object]],
products: dict[str, Decimal]
) -> list[CartItem]:
valid_items: list[CartItem] = []

```
for index, raw_item in enumerate(
    raw_cart,
    start=1
):
    raw_name = raw_item.get("name")

    if not isinstance(raw_name, str):
        print(
            f"Skipped item {index}: "
            "invalid product name."
        )
        continue

    name = raw_name.strip().lower()

    if name not in products:
        print(
            f"Skipped item {index}: "
            f"unknown product '{name}'."
        )
        continue

    try:
        quantity = parse_quantity(
            raw_item.get("quantity")
        )

    except ValueError as error:
        print(
            f"Skipped item {index}:",
            error
        )
        continue

    valid_items.append(
        CartItem(
            name=name,
            quantity=quantity
        )
    )

return valid_items
```

def calculate_subtotal(
cart: list[CartItem],
products: dict[str, Decimal]
) -> Decimal:
subtotal = Decimal("0.00")

```
for item in cart:
    price = products[item.name]

    item_total = (
        price
        * item.quantity
    )

    subtotal += item_total

return round_money(subtotal)
```

def calculate_tax(
subtotal: Decimal
) -> Decimal:
return round_money(
subtotal * TAX_RATE
)

def calculate_delivery(
subtotal: Decimal
) -> Decimal:
if subtotal >= FREE_DELIVERY_MINIMUM:
return Decimal("0.00")

```
return DELIVERY_CHARGE
```

def display_receipt(
cart: list[CartItem],
products: dict[str, Decimal]
) -> None:
subtotal = calculate_subtotal(
cart,
products
)

```
tax = calculate_tax(subtotal)

delivery = calculate_delivery(
    subtotal
)

total = round_money(
    subtotal
    + tax
    + delivery
)

print()
print("SHOPPING CART RECEIPT")
print("=" * 45)

for item in cart:
    price = products[item.name]

    item_total = round_money(
        price * item.quantity
    )

    print(
        f"{item.name.title():<15} "
        f"{item.quantity:>3} "
        f"x ${price:>6} "
        f"= ${item_total:>7}"
    )

print("-" * 45)
print(
    f"{'Subtotal':<30} "
    f"${subtotal:>7}"
)
print(
    f"{'Tax':<30} "
    f"${tax:>7}"
)
print(
    f"{'Delivery':<30} "
    f"${delivery:>7}"
)
print(
    f"{'Total':<30} "
    f"${total:>7}"
)
```

def main() -> None:
cart = build_cart(
RAW_CART,
PRODUCTS
)

```
if not cart:
    print(
        "The cart contains no valid items."
    )
    return

display_receipt(
    cart,
    PRODUCTS
)
```

if **name** == "**main**":
main()
```

Output

Skipped item 3: unknown product 'salad'.
```

# SHOPPING CART RECEIPT

Pizza            2 x $ 14.99 = $  29.98
Burger           3 x $  9.99 = $  29.97
---------------------------------------

Subtotal                       $  59.95
Tax                            $   7.79
Delivery                       $   0.00
Total                          $  67.74
```

Project Explanation

The corrected program validates each raw cart item before calculation. Product names are cleaned and checked against the available product dictionary. Quantities are converted to integers and must be greater than zero.

Invalid items are skipped with useful messages instead of causing the complete application to fail. Known products are converted into structured CartItem objects.

Item totals use multiplication, and the subtotal uses += so every valid item contributes to the final amount. The tax rate is represented as 0.13.

The delivery charge becomes zero when the subtotal reaches the free-delivery minimum. Decimal arithmetic and explicit rounding are used for money calculations.

How to Run the Project

  1. Create a file named debugging_project.py.
  2. Copy the faulty version into the file first.
  3. Run it and record the first error.
  4. Correct only the syntax problem.
  5. Run it again and inspect the next problem.
  6. Add focused debugging output.
  7. Set breakpoints inside the cart loop.
  8. Inspect the product name, quantity, price, item total, and subtotal.
  9. Replace the faulty program with the corrected version.
  10. Run python debugging_project.py.
  11. On some computers, run python3 debugging_project.py.

Project Challenges

  • Add a discount code and debug its calculation.
  • Add pickup and delivery options.
  • Add duplicate products and combine their quantities.
  • Add a conditional breakpoint for quantities above ten.
  • Add an invalid negative quantity and trace it.
  • Write skipped-item messages to a log file.
  • Load the cart from a JSON file.
  • Handle a missing JSON file.
  • Handle malformed JSON data.
  • Create automated tests for every corrected bug.
  • Create a regression test for the tax calculation.
  • Create a regression test for free delivery.
  • Use pdb to inspect the subtotal calculation.
  • Use the VS Code call stack while inside round_money().
  • Write a debugging report describing each bug and correction.
```
Chapter 2: Development EnvironmentPrepare Your JavaScript Workspace

A modern course built to help learners study step by step with clarity, comfort, and confidence.