Why can Python run a program without displaying any error and still give the wrong answer? A syntax error stops code from being parsed correctly, while a runtime error occurs during execution, and a logic error can leave a program running but producing an incorrect result. Recognising the difference helps students decide whether to correct the structure of a statement, investigate an exceptional operation or trace the reasoning behind a calculation.
Ministry of Education V3.0 Reference 393 explains the 2027 Singapore–Cambridge SEC G3 Computing K349 outcomes 2.4.8 and 2.4.9. It distinguishes syntax, logic and runtime errors, then teaches how to design tests covering normal, error and boundary conditions. The guide includes trace tables, backtracking, diagnostic print statements, incremental tests, commenting out irrelevant sections and twenty-six original cases. The aim is to help students locate the earliest incorrect state rather than change the final printed answer until it looks acceptable.
Quick notes: syntax errors concern invalid code structure; runtime errors happen while a program runs; logic errors cause unsuitable output without necessarily causing an exception. Normal tests examine ordinary accepted cases, error tests examine invalid or exceptional inputs, and boundary tests examine values at or near limits. Record expected results before execution and distinguish invalid interactive inputs from invalid file inputs according to the task. This independent eduKateSG guide is not an official MOE, SEAB or Cambridge publication. All programs and records are fictional learning examples.
1. The precise official syllabus outcomes
K349 2.4.8 requires students to understand and describe syntax, logic and runtime errors and explain why they occur. Outcome 2.4.9 requires test cases covering normal, error and boundary conditions, with identification of the type or types tested. The earlier testing outcomes explain trace tables, print-based inspection, backtracking, incremental development and commenting out unrelated code.
These skills connect but should not be conflated. A syntax error is a property of code parsing; a boundary test is a kind of selected input. A normal test can reveal a logic error, and an error-condition test can trigger a runtime error. The two classifications describe different aspects of the work.
2. A syntax error prevents correct parsing
A missing colon after an if statement, unmatched parentheses or invalid statement structure can cause Python to report SyntaxError. The program cannot execute that statement as intended because the language grammar has not been satisfied. The fix begins by inspecting the indicated line and surrounding structure.
For example, `if score > 50` without a colon is not a valid complete if header. Adding the colon addresses that syntactic problem; it does not automatically prove the condition itself is the right one for the requirement. Students should separate syntax repair from later semantic testing.
3. A runtime error occurs during execution
A program may contain syntactically valid statements but fail when a particular operation is executed. Division by zero, attempting to convert unsuitable text into an integer, accessing an invalid list position or using an undefined name can produce runtime exceptions such as ZeroDivisionError, ValueError, IndexError or NameError.
The error is connected with the program’s execution state and input. A function containing a dangerous division might run safely for nonzero divisors and fail for zero. Students should identify the exact operation and the state that triggered it rather than claim the entire source was syntactically invalid.
4. A logic error can survive every exception check
Logic errors occur when the implemented reasoning does not meet the problem’s intended behaviour. A total calculation that subtracts a quantity instead of adding it might produce a plausible number while raising no Python exception. A loop that stops one iteration early can likewise finish normally but omit data.
The key evidence is a mismatch between expected and actual results. Merely observing that a program starts and completes is insufficient to establish correctness. A student needs an independent expected outcome based on the specification and test input.
5. Syntax, runtime and logic describe different failure locations
Suppose a student writes a condition without the required punctuation. That is syntax. After fixing it, the code may access an element beyond a list’s length, causing a runtime exception. After fixing the bounds, it may still select the wrong item because the index was based on one rather than zero. That is a logic error.
One program can therefore contain several defects in sequence. Correcting one does not prove the others are absent. The most efficient method is to resolve the earliest detectable failure and then rerun a defined test suite to check subsequent behaviour.
<
!
–
6. Define expected output before running code
Testing is stronger when the learner writes the expected result independently. For list [2,3,4], the correct arithmetic sum is nine. If the program prints minus nine, the difference is evidence of a logic error. Without the independent result, a printed number can appear credible.
An expected result can come from a manual calculation, a simple case whose result is obvious or a trusted specification. The testing process should not alter the expected value merely to match what the defective program happened to print.
7. Normal tests use ordinary valid inputs
A normal test exercises representative accepted inputs away from special limits. For a quantity allowed from zero to one hundred inclusive, a value such as fifty is a straightforward normal case. It checks whether the program handles a typical valid input according to the defined rules.
One normal case is insufficient to establish correctness across the entire domain. A misplaced greater-than sign can still give the expected answer for fifty while failing at a threshold. A complete suite includes boundary and invalid cases in addition to representative ordinary ones.
8. Boundary tests focus on transitions and endpoints
If valid ages are from 13 through 16 inclusive, test 12, 13, 16 and 17. The values immediately outside and exactly on each limit reveal whether a comparison uses > versus >= or < versus <=. For a sequence of length five, indices four and five help test the last valid position and the first invalid position.
The exact test values depend on type and step size. For integers, one below and one above can be natural; for decimals, choose values appropriate to the specified precision. The educational principle is to test where expected behaviour changes, not merely values that are large or small.
9. Error-condition tests examine invalid situations
Error tests use inputs or conditions the program is not meant to accept, or exceptional situations that require defined handling. A negative item quantity, a nonnumeric string in a numeric field, a missing required field or division by zero can serve as an error case when the specification declares them invalid.
An invalid-input test should have an expected response. Interactive input may require another prompt, while a non-interactive file task may require program exit under the K349 outcome. Simply crashing is not automatically the desired behaviour; follow the exact task contract.
10. Test categories can overlap
A boundary value just outside the permitted interval is both a boundary case and an invalid-input case. Thus a test of quantity -1 when valid quantities start at zero is a boundary test that also checks error handling. The syllabus explicitly invites students to specify which type or types of conditions a test covers.
Do not force every case into exactly one category when the question permits multiple labels. Instead identify why each test was selected: an ordinary valid path, a transition point or an invalid or exceptional condition. The classification should match the actual input requirements.
11. A trace table reveals the first wrong state
A trace table can record each iteration’s input element, the loop counter, running total and expected state. If a summation should produce 2, then 5, then 9 as it processes [2,3,4], a table showing 2, 4, 8 reveals that the wrong update happened before the final output.
The earlier point of divergence is usually more useful than the last incorrect number. A student can compare each line of manual arithmetic with the program’s state and locate the statement that changed the expected value.
12. Print statements can expose intermediate values
K349 2.4.2 explicitly mentions inserting print statements to inspect variable values at selected steps. A student might print the current item and accumulator inside a loop, then remove the diagnostic statements once the logic is understood. The output supports investigation, not just presentation.
A large number of prints can become confusing, so choose points that separate possible causes. For a failing file calculation, inspect the parsed input values before calculating, then inspect the total after each item. This helps determine whether the problem begins with reading or with arithmetic.
13. Backtrack from an unexpected result
K349 2.4.3 describes locating logic errors by backtracking from the point where unexpected behaviour is observed. If the final total is wrong, ask which assignment produced it, then inspect the previous total and current input. Continue backwards until the first inconsistency appears.
Backtracking is not a reason to change code randomly. The student should form a hypothesis, select a revealing input and observe the state. A correction is stronger when it explains the failure rather than merely changing the number that appears in the final output.
14. Incremental testing reduces uncertainty
K349 2.4.4 asks students to test programs incrementally as small changes are made. If an existing loop correctly calculates a total, add one feature such as output formatting, then retest earlier cases before adding file input. This narrows the changes that could have introduced a defect.
One giant test at the end of a long program leaves many possible causes. Incremental testing provides evidence that each new stage works with earlier stages. It does not guarantee complete correctness, but improves diagnosis and prevents avoidable accumulation of defects.
15. Commenting out unrelated sections can isolate an issue
K349 2.4.5 asks students to test small parts by commenting out other parts not needed for the test. For example, a student may temporarily disable an output-formatting block while checking the core calculation. This can reduce noise in the trace and focus attention on the suspected area.
Comments should not be used to hide a failed requirement in the final submission. After isolating and correcting the problem, re-enable the relevant sections and retest the complete program. A correctly working fragment is not proof that its integration with other parts is correct.
16. An exception message is a clue, not a complete explanation
An IndexError suggests an out-of-range access in the relevant execution path, but a student must still identify why the program requested that index. It might be an incorrect loop boundary, an empty list or an assumption about input length. Repairing the symptom without the cause can create another error.
A NameError identifies an unresolved name; this can arise from a misspelling, missing definition or scope misunderstanding. The proper response is to inspect the name and execution order. Students should learn to translate an error message into a hypothesis and verify it.
17. Test the return type and output destination
A function may display the expected answer with print but return None to its caller. That can cause a later runtime error when the caller expects a number. Similarly, a program may compute a correct result but write it to the wrong file, which is an output requirement failure rather than necessarily a runtime exception.
A complete test verifies what was requested: numeric content, type, text formatting, order and file path where relevant. The absence of a Python traceback does not show that these requirements were met. This connects testing with the input-output contract covered in Reference 387.
18. A failed test is useful evidence
A test that reveals a bug is not a wasted attempt. It identifies a discrepancy between the specified and actual behaviour and gives the learner information to refine the program. The quality of a test comes from which failure it can expose, not from whether the code passes every test on the first run.
A good student records the input, expected output, actual outcome, error classification and intended correction. After editing, rerun that test and relevant earlier tests. This demonstrates an evidence-based correction process rather than a sequence of guesses.
19. Distinguish validation from program error categories
An invalid user input is not by itself proof that the program contains a syntax error or logic error. A well-designed program can recognise invalid input and respond correctly. Conversely, a valid input can reveal a logic error if the algorithm uses the wrong operator.
Test classifications describe the nature of the input or test condition, while program-error classifications describe the failure mechanism. A carefully labelled test table can show both without mixing them. This makes exam answers more precise.
20. The strongest SEC explanation is causal
For each case, name the program statement or assumption, classify the error and explain why it occurs. For each chosen input, identify whether it is normal, boundary, error or a combination, and state the expected response. These are the two different learning outcomes the syllabus asks students to demonstrate.
Then use a trace table, diagnostic print or controlled isolation to locate the earliest wrong state. A correction should be backed by retesting. The purpose is not simply to make code run, but to show that the implemented behaviour agrees with a clearly specified problem.
21. Twelve-stage Error–Test–Trace protocol
This independent eduKateSG protocol is a learning method, not an official examination marking rubric. It separates the diagnosis of a program failure from the classification of the test input and makes each correction verifiable.
Stage 1 — Name the required behaviour
Write what the program must do and what results should be produced for a valid input. A program cannot be called correct without a contract.
Stage 2 — Identify the observed failure
Distinguish a parser complaint, an exception while running, and a finished program with an unexpected result.
Stage 3 — Classify the program error
Use syntax for invalid grammar, runtime for an execution failure, and logic for a wrong algorithmic decision or output.
Stage 4 — Choose one normal input
Select an ordinary valid value where the expected result is straightforward, to establish a working baseline.
Stage 5 — Choose boundary inputs
Test values at and near each inclusive or exclusive limit or final valid index, based on the actual data type.
Stage 6 — Choose invalid or exceptional inputs
Test absent, malformed, out-of-range or zero-divisor cases according to the input contract and expected error policy.
Stage 7 — Label the test condition types
A just-outside-the-range case may be both a boundary and an error test; provide all justified classifications.
Stage 8 — Predict each expected outcome
Use manual reasoning or a trusted specification before running code, so the expected result is not copied from the current bug.
Stage 9 — Trace relevant state
Record only the variables and statements needed to find when actual behaviour first differs from the prediction.
Stage 10 — Insert useful diagnostic prints
Display a counter, input or intermediate total at selected steps rather than flooding the entire program with meaningless output.
Stage 11 — Isolate the failing component
Temporarily comment out unrelated sections when permitted, test the small part, then restore full integration.
Stage 12 — Repair and rerun
Explain the cause, fix it, rerun the failed test and check earlier normal and boundary cases for regressions.
22. Twenty-six original program-error and test cases
Worked case 1 — Missing if colon
An if header without its required colon causes a syntax error because the statement does not satisfy Python’s grammar. Repairing the colon does not prove the condition itself is correct.
Worked case 2 — Unclosed parenthesis
A function call with an unmatched opening parenthesis can produce a syntax error. The program cannot parse the intended grouping before execution.
Worked case 3 — Incorrect indentation
An invalid indentation structure can prevent Python from parsing a block as written, whereas a valid but misplaced block can create a logic error.
Worked case 4 — Undefined variable
Attempting to print a name that has never been defined in the executing scope can cause NameError at runtime. The issue may involve spelling or execution order.
Worked case 5 — Division by zero
For denominator zero, 8 / 0 raises ZeroDivisionError when evaluated. A valid-input contract should exclude or appropriately handle that case.
Worked case 6 — Invalid numeric conversion
int(‘ten’) raises ValueError because the string is not suitable decimal integer text. The issue occurs during conversion, not simply when the source file is read.
Worked case 7 — Out-of-range list index
For a three-element list, accessing index three causes IndexError. The positive indices are zero through two; a loop boundary mistake may produce the invalid access.
Worked case 8 — Wrong operation in a sum
An algorithm subtracts each item rather than adding it. It can run and print a number but fails the specified arithmetic requirement, a logic error.
Worked case 9 — Off-by-one iteration
A loop intended to process five items visits only four. It finishes without a traceback but omits the final observation, producing a logic error.
Worked case 10 — Wrong comparison
A rule intended to include 60 uses >60 instead of >=60. The result is wrong exactly at the threshold, a logic error exposed by a boundary test.
Worked case 11 — A normal accepted value
For valid integers from zero through 100, input 50 is an ordinary normal test. It checks the accepted path rather than a boundary.
Worked case 12 — The lower valid boundary
Input zero is a valid boundary test for a nonnegative integer range. It may reveal code that incorrectly uses >0 rather than >=0.
Worked case 13 — Just below the lower boundary
Input -1 is an invalid case and a boundary-adjacent case. It tests both the range limit and the required error response.
Worked case 14 — The upper valid boundary
Input 100 is a valid boundary test for a 0-to-100 inclusive range. It exposes incorrect strict upper-limit comparisons.
Worked case 15 — Just above the upper boundary
Input 101 is invalid and near the upper boundary. A correct validator should handle it according to the stated task.
Worked case 16 — An empty required field
A blank entry violates a presence requirement when the task says the field is compulsory. The expected response should be defined before testing.
Worked case 17 — A numeric-looking string
A field contains ’12’ but the program expects a numerical value. Conversion may be needed; silently concatenating strings can produce a logic error.
Worked case 18 — A return versus print mistake
A function prints 12 but implicitly returns None. A caller expecting to use the returned value in arithmetic may experience a later runtime TypeError.
Worked case 19 — A wrong output file
The program calculates 14 correctly but saves it under an unintended filename. It violates the output contract without necessarily causing a Python exception.
Worked case 20 — A trace-table divergence
The expected running totals over [2,3,4] are 2, 5 and 9. A trace showing 2,4,8 reveals a wrong update before the final result.
Worked case 21 — Backtracking from a wrong total
A program prints a bad total. Inspect the last accumulator update, preceding value and input item, then work backwards to the first mismatch.
Worked case 22 — A useful diagnostic print
A loop prints the index and accumulator after each update. This helps locate which iteration first changed the expected state.
Worked case 23 — Testing after a small addition
A previously working sum function gains output formatting. Retesting the sum and new display isolates defects introduced by that change.
Worked case 24 — Temporarily isolating a component
A student comments out unrelated file-writing code while testing the numerical calculation. The output code must be restored and integrated tests rerun before completion.
Worked case 25 — A normal test hides a boundary bug
A threshold classification works for 70 but incorrectly handles 60. The ordinary test passes; the exact boundary reveals the incorrect comparison.
Worked case 26 — A complete test suite
For a range 0–100, use values 50, 0, 100, -1 and 101 plus malformed text when relevant, recording expected responses and condition types.
23. Frequently asked questions
Can a program have a logic error without an exception?
Yes. It may execute normally but produce a wrong result because the algorithm’s condition, arithmetic or sequence is incorrect.
Is every crash a syntax error?
No. Runtime exceptions occur while syntactically valid code executes particular operations or inputs.
What is a normal test case?
A representative accepted input in the ordinary valid region, used to check typical intended behaviour.
What is a boundary test?
An input at or close to a point where accepted behaviour changes, such as a valid endpoint or just-outside value.
What is an error-condition test?
An invalid or exceptional input or situation with a specified expected response.
Can a boundary test also be an error test?
Yes. A value just outside a valid range can test both the boundary and handling of invalid input.
Why predict results before execution?
An independent expected result makes logic errors visible instead of accepting whatever the defective program prints.
Why use a trace table?
It reveals the values of variables after key steps and helps locate the earliest divergence from the intended computation.
What does backtracking mean in debugging?
Starting from the unexpected result and examining preceding statements and states until the underlying error is found.
Should diagnostic print statements remain forever?
Not necessarily. They can be removed or adapted after investigation according to the task’s required final output.
Does fixing syntax guarantee correctness?
No. The program still needs runtime, logic, normal, boundary and error testing.
What should be in a K349 answer?
Explain the error’s cause and identify the input’s test condition type, expected response and evidence from a trace.
24. Official syllabus and connected eduKateSG resources
The authoritative reference is the 2027 SEC G3 Computing K349 syllabus, specifically 2.4.1–2.4.9, including 2.4.8 for error types and 2.4.9 for normal, error and boundary conditions. Related eduKateSG guides: Reference 357 — Validation and Debugging, Reference 387 — Problem Analysis, Reference 392 — Algorithm Design, the MOE V3.0 library and the immutable Clementi teaching standard.
Editorial review: 2 November 2026, Singapore. All code and test cases are original fictional examples. Actual test data should be chosen from the stated program requirements, and a program that runs successfully still needs independent output verification.
25. A fictional school attendance test plan
A classroom program accepts an integer number of attendees from zero through one hundred inclusive. The output is the label ‘Valid count’ for accepted values and the stated invalid-input response otherwise. A normal test could use fifty. Boundary tests should include exactly zero and exactly one hundred, plus the immediate outside integers minus one and one hundred and one. The outside cases also serve as error-condition tests because they violate the permitted range.
Now introduce text ‘fifty’ rather than the numeric string ’50’, and a genuinely blank input. These are error tests under a contract requiring a numeric, present value. Write the expected response before running Python. If the program crashes on the text rather than handling it as required, classify the execution issue accurately and inspect whether conversion is attempted before validation. A logically correct classification expression cannot compensate for an incorrect input-handling path.
Finally change only the lower-bound comparison from >=0 to >0. The ordinary test at fifty still passes, but the valid boundary at zero now fails. This demonstrates why normal tests alone are insufficient and why a carefully selected boundary value can expose one exact logic mistake.
26. A program that runs yet fails its own arithmetic
Suppose a fictional program calculates the total of [2,3,4]. The expected total is nine. Its loop mistakenly subtracts every input from the accumulator, printing minus nine without a Python exception. The program is syntactically valid and executes to completion, so the error belongs to the logic rather than syntax or runtime failure.
A useful trace table records the current input and running total after each iteration. Under the intended addition, the totals should be two, five and nine. Under subtraction they become minus two, minus five and minus nine. The divergence begins at the first update, which identifies the likely defective operation. Editing only the final printed value to nine would conceal rather than fix the underlying algorithm.
After changing the update to addition, rerun the original normal test and add an empty list and a list with zero, under whatever input conditions the task permits. If new features are subsequently added, retain these tests. The correction is justified by the expected invariant that the running total equals the sum of items processed so far.
27. Testing after a runtime repair
A fictional script reads a count from text and divides a total by it. With input ‘5’, conversion and division can succeed. With input ‘0’, a division-by-zero exception may occur. With input ‘five’, integer conversion may raise a ValueError. These are different runtime mechanisms triggered by different inputs, even though both cases are unsuitable for the intended calculation.
If the program’s contract requires a positive integer divisor, the correct design should recognise invalid zero and unsuitable numeric strings and use the specified response. A student should not simply catch every exception and return a made-up quotient. The resulting data would be misleading, and the program would still fail to meet its defined behaviour.
Testing the repaired code includes one ordinary divisor such as five, one minimum permitted divisor such as one, invalid zero, and malformed text where relevant. Recording expected versus actual outcomes and the condition type supports a defensible diagnosis beyond merely making an exception message disappear.
