Every distinction begins with one topic at a time.- start now
if or case, repeating with a loop, and displaying the answer in the format the question demands. Marking is per step, so code that does not compile still collects ticks for every correct line.
The compiler is not the marker.· every correct line earns its tick even when the program never runs
div and mod on any pair of integersStrToInt, IntToStr, StrToFloat and FloatToStrF so output matches the required layoutif, a nested if and case, and explain why case accepts only ordinal typeswhile and repeatLength, Pos, Copy, Delete and Insert, and split a delimited string into its partsvar with a type that fixes what may be stored in it. A constant is declared under const, is given its value once, and may never be assigned to again. Assignment in Delphi is written :=, while a single = is a test for equality. Mixing those two up is a compile error, not a logic error.
| Type | Holds | Declared as | What trips learners up |
|---|---|---|---|
| Integer | Whole numbers, positive or negative | iCount : Integer; | Cannot hold 3.5; assigning a Real to it will not compile |
| Real | Numbers with a decimal fraction | rTotal : Real; | Never use it for money you intend to compare exactly |
| Boolean | True or False only | bPaid : Boolean; | Writing if bPaid = True works but if bPaid is cleaner |
| Char | Exactly one character | cGrade : Char; | Single quotes, so 'A', never "A" |
| String | Any number of characters | sName : String; | Indexed from 1, not from 0 |
Scope is the region of the program in which a declared name is visible. A variable declared inside a procedure is local: it is created when the procedure starts, destroyed when it ends, and no other procedure can see it. A variable declared at the top of the unit, outside every procedure, is global and every procedure in that unit can read and change it. Globals look convenient and behave badly, because any procedure can quietly overwrite one, so the fault shows up somewhere far from the line that caused it. Declare locally unless two event handlers genuinely have to share the value.
/ where the question wants div is the single most reliable way to throw a mark away in Section A. In Delphi, / always produces a Real, even for 10 / 2, so it cannot be assigned to an Integer variable. div gives the whole-number part of a division and mod gives the remainder left over. Both work on integers only.
47 div 5 gives 9, the whole times 5 fits into 4747 mod 5 gives 2, what is left over47 / 5 gives 9.4, a Real value8 mod 2 gives 0, which is the standard test for an even number227 div 60 is 3 hours, 227 mod 60 is 47 minutes58 div 12 is 4 full boxes, 58 mod 12 is 10 left overif iNum mod 3 = 0 means "is a multiple of 3"iNum mod 10not, then * / div mod and, then + - or, then the comparisons. Two consequences bite in the exam. First, and binds tighter than =, so a compound condition such as if (iA > 5) and (iB < 3) must have brackets around each comparison or it will not compile. Second, when in doubt add brackets: they cost nothing and they never lose a mark.
Everything a user types into an Edit box arrives as a String, and everything you display must go back out as a String. Converting between the two is where the marks for "display the answer as R1 234,50" actually live.
| Function | Turns | Example | Result |
|---|---|---|---|
StrToInt | String into Integer | iQty := StrToInt(edtQty.Text); | Raises an error if the text is not a whole number |
StrToFloat | String into Real | rMass := StrToFloat(edtMass.Text); | Respects the machine's decimal separator |
IntToStr | Integer into String | lblOut.Caption := IntToStr(iQty); | '12' |
FloatToStrF | Real into a formatted String | FloatToStrF(rTot, ffFixed, 8, 2) | '1234.50', fixed to 2 decimals |
Round | Real into nearest Integer | Round(9.6) | 10 |
Trunc | Real into Integer by cutting | Trunc(9.9) | 9, the fraction is discarded, never rounded |
Frac | Real into its fraction only | Frac(9.25) | 0.25 |
The four arguments of FloatToStrF are the value, the format, the total width and the number of decimals, so ffFixed, 8, 2 reads as "fixed-point, at most 8 digits, exactly 2 after the point". Where a currency prefix is asked for, concatenate it: 'R' + FloatToStrF(rTot, ffFixed, 8, 2). Note also that Round in Delphi rounds a value ending in exactly .5 to the nearest even integer, so Round(2.5) gives 2 while Round(3.5) gives 4. It almost never matters in an exam answer, but it explains a result that otherwise looks like a bug.
FloatToStrF(rAnswer, ffFixed, 8, 2) earns its own tick on the marking grid. Never skip the display line because you are unsure of the sum.
case works only on ordinal types, which means Integer, Char, Boolean and enumerated types. It cannot switch on a String and it cannot switch on a Real, because those have no fixed list of discrete values to jump to. A question that hands you a single letter code is inviting a case; a question that hands you a name or a price is inviting an if ladder. Choosing the wrong one is a compile error you will not have time to unpick.
if rPercent >= 80 thensSymbol := 'A'else if rPercent >= 70 thensSymbol := 'B'else if rPercent >= 50 thensSymbol := 'C'elsesSymbol := 'F';case iDay of1..5 : sType := 'Weekday';6, 7 : sType := 'Weekend';else sType := 'Invalid';end;Two details cost marks quietly. There is no semicolon before else in an if statement, because the else belongs to the same statement. And when a branch must do more than one thing, wrap those statements in begin and end; without them only the first line belongs to the branch and the rest runs every time.
| Loop | Use it when | Condition tested | Can it run zero times? |
|---|---|---|---|
for i := 1 to N do | The number of repeats is known before the loop starts | Automatically, by the counter | Yes, if N is less than the start value |
while <condition> do | You repeat until something becomes true, and it may already be true | At the top, before the body | Yes |
repeat ... until | The body must happen at least once, such as asking for input | At the bottom, after the body | No, it always runs at least once |
That zero-iteration difference is examined directly. If a list is empty, a while that reads it never executes its body and the program continues safely; a repeat in the same position reads once before it checks, and that first read is what crashes on empty data. Note also that repeat ... until needs no begin and end, because repeat and until already bracket the body, while a while or for body of more than one statement does.
for loop, or forgetting to move the variable a while loop tests, produces an infinite loop. In a three-hour practical paper an infinite loop costs you the question and the time. Before you run anything, check that the value in the loop condition is changed somewhere inside the body.
| Routine | What it does | Worked example on sTxt = 'Cape Town' |
|---|---|---|
Length(s) | Number of characters | Length(sTxt) gives 9 |
Pos(sub, s) | Position where a substring starts, or 0 | Pos(' ', sTxt) gives 5 |
Copy(s, start, count) | Pulls out part of the string | Copy(sTxt, 1, 4) gives 'Cape' |
Delete(s, start, count) | Removes part, changing the variable itself | Delete(sTxt, 1, 5) leaves 'Town' |
Insert(new, s, at) | Pushes text in at a position | Insert('X', sTxt, 1) gives 'XCape Town' |
UpperCase / LowerCase | Changes the case of a whole string | UpperCase(sTxt) gives 'CAPE TOWN' |
Trim(s) | Strips spaces from both ends | Use it on every value read from an Edit box |
Strings are indexed from 1, and Pos returns 0 when the substring is not there at all. Both facts matter, because the standard splitting routine leans on them: find the delimiter with Pos, take everything in front of it with Copy, then Delete that part plus the delimiter and repeat until Pos returns 0. Compare characters with UpperCase applied to both sides so that 'y' and 'Y' are treated alike.
while Pos('#', sLine) > 0 dobeginsPart := Copy(sLine, 1, Pos('#', sLine) - 1);redOut.Lines.Add(sPart);Delete(sLine, 1, Pos('#', sLine));end;redOut.Lines.Add(sLine); // the last part has no # after itDate returns today, Now returns today with the timeFormatDateTime('yyyy/mm/dd', dDay) lays a date out for displayDaysBetween(dEnd, dStart) counts whole days, and needs the DateUtils unitdDue < Date is a valid test for "overdue"Sqr(x) squares, Sqrt(x) takes the square rootPower(x, y) raises to a power, and needs the Math unitRandom(10) gives 0 to 9; RandomRange(1, 7) gives 1 to 6Inc(i) and Dec(i) add or subtract onePi is a built-in constant, so never type 3.14Randomize is called once, usually in the form's OnCreate, and it is what makes Random produce a different sequence each run. Without it, the same "random" numbers appear every time the program starts. If a question asks for random values, the call to Randomize is often a mark on its own.
A trace table is a table with one column per variable and one row per pass through the loop. You fill it in by hand, playing the part of the computer, and the row where the answer first goes wrong tells you which line to fix. Paper 2 examines the table itself as a design tool; Paper 1 rewards it because it is faster than running a broken program three times.
Length(s) - 1 instead of Length(s), and both of those show up in the first and last rows only.edtQty and a unit price into edtPrice.iQty := StrToInt(Trim(edtQty.Text));rPrice := StrToFloat(Trim(edtPrice.Text));rTotal := iQty * rPrice;if iQty >= 10 thenrTotal := rTotal * 0.85;iBoxes := iQty div 6;iLoose := iQty mod 6;lblOut.Caption := 'Total: R' + FloatToStrF(rTotal, ffFixed, 8, 2)+ ' in ' + IntToStr(iBoxes) + ' box(es) plus ' + IntToStr(iLoose) + ' loose';Check it against a value you can verify by hand. With 14 items at R7,50 each, the raw total is R105,00, the discount takes it to R89,25, and 14 items pack into 14 div 6 which is 2 boxes with 14 mod 6 which is 2 loose. Every one of those five lines is a separate tick on the marking grid, which is why a partially finished answer is always worth writing.
iMins. The display must read the time as whole hours plus the remaining minutes. Which pair of expressions produces the hours and the leftover minutes correctly?(2)
case sProvince of where sProvince is declared as a String, and the project refuses to compile. What is the reason, and what should be used instead?(2)
iSpot := Pos('@', sEmail); the user has typed an address with no @ sign in it at all. What value does iSpot hold afterwards?(2)
Trunc(9.9)?(2)
while loop and a repeat ... until loop, and describe one situation with empty data where choosing the wrong one causes the program to fail.(4)
while iCount < 10 do loop whose body reads a value and displays it but never alters iCount. Explain why the program freezes and state the correction.(4)
div and mod, using sweets shared among friends?
Questions reworked from: DBE NSC Nov 2023 P1, Nov 2024 P1, Nov 2025 P1.
/ gives a Real, div gives the whole part and mod the remainder, and brackets settle every precedence argument. Convert input with StrToInt and StrToFloat, and display with IntToStr or FloatToStrF(value, ffFixed, 8, 2). Choose with an if ladder for Reals and Strings and with case for ordinal values only. Repeat with for when the count is known, while when the body may run zero times, and repeat when it must run at least once. Handle text with Length, Pos, Copy, Delete and Insert, remembering that strings index from 1 and Pos returns 0 on a miss. Desk-check a suspect loop by hand rather than guessing at the code, and remember that marking is per step, so an incomplete answer still scores.
div and mod. Nothing else fits.This is a free preview of Topic 1. The full guide has every topic, the self-marking quizzes, mock exams, planner, journal and wellbeing tools.
Unlock the full guide on WhatsAppSee pricingA winner is a loser who tried one more time.- keep going, all the way to the exam