Programming glossary 42 terms mentioned in this lesson
Select any term for a clear definition. The return button brings you back to the exact term link you used.
Lesson: Understanding Variables
A variable is an element of your program that represents some state. This state may, and is expected to, change during the execution of your program. Variables are the moving parts of your program — where errors and bugs often emerge. To use them effectively, you must understand both their purpose and their risks.
There are a few rules to consider when creating variables:
- Only create them if required – The more variables you create, the more opportunity for failure, since each can be misused. Novice programmers often introduce unnecessary variables. You can usually tell when a variable is redundant if it appears on the left-hand side of an assignment operator (
=) only once (or not at all!). - Give them meaningful identifiers – An identifier is the name you assign to the variable. While many terms in a programming language are predefined, variable names are under your control. Clear, descriptive names reduce errors and make your program easier to understand.
- Variables are source code only – They exist as named elements in your code, but once compiled they are translated into memory locations and CPU registers. Identifiers disappear during compilation. This means memory itself is “variable,” and even parts of your program not defined as variables can be overwritten by incorrect use of the ones you created.
- Variables define program correctness – Assuming a program compiles successfully and runs, errors occur only at the point of state changes — in other words, when variables are manipulated. This is why every variable you add is another opportunity for error. Use them sparingly and keep their scope narrow.
This is why variables are central to debugging: they are the “pressure points” of your program. Managing them well leads to correctness, clarity, and performance.
Example: Variables in Action
Here’s a simple C++ program using variables correctly:
// Calculate the average of three exam scores
#include <iostream>
using namespace std;
int main() {
int score1 = 78; // meaningful identifier
int score2 = 85;
int score3 = 92;
int sum = score1 + score2 + score3; // variable with clear purpose
double average = sum / 3.0; // avoid reusing "sum" incorrectly
cout << "Average score: " << average << endl;
return 0;
}
Notice that every variable has a purpose, a clear name, and is used more than once. If we had introduced an extra variable like int temp = score1; that was never used again, it would add confusion and risk without benefit.
Programming Insight (AI) — Smarter Use of Variables
- Code review: Ask AI to highlight variables that are created but used only once.
- Naming help: AI can suggest clearer identifiers (e.g.,
totalMarksinstead ofsum). - Memory focus: AI can explain how each variable compiles down to memory/registers.
- Debugging: Paste an error trace into AI and ask which variable transitions to watch first.
Debugging and Variables
Because errors occur when variables change, they become the focal point of debugging. Watching variables, setting breakpoints, and testing edge cases all revolve around monitoring state. A good programmer treats variables as critical checkpoints in their program.
Programming Insight (AI) — Debugging with Variables
- AI can generate watch lists for your IDE (which variables to track and why).
- It can scaffold unit tests that drive variables into edge cases (e.g., division by zero, negatives).
- AI can narrate expected state transitions step by step, so you compare “expected” vs “actual.”
Deep Dive: Why Variables Matter
Think of variables not just as storage, but as the moving parts of your program. Errors almost always happen when values change. A well-chosen, well-placed variable acts like a clearly labeled valve in a system: easy to monitor, easy to repair. A poorly chosen variable is like an unmarked pipe — you don’t know what flows through it, and when it leaks, chaos ensues.
As you progress, remember: fewer, clearer variables make debugging and reasoning much easier.
Mini Exercise — Audit & Refactor Your Variables
Goal: practise spotting redundant variables, tightening scope, choosing clear identifiers, and debugging by watching state changes.
-
Spot the redundancies
In the snippet below, which variables are unnecessary or risky? Rewrite it with fewer, clearer variables.// Compute average and pass/fail (threshold 50) #include <iostream> using namespace std; int main() { int a = 78; // exam 1 int b = 85; // exam 2 int c = 92; // exam 3 int tmp = a; // <-- suspicious int total = 0; total = a + b + c; double avg; // <-- declared early, uninitialised avg = total / 3.0; bool t = true; // <-- defaulted, then overwritten if (avg < 50.0) { t = false; } cout << "Average: " << avg << " passed? " << (t ? "yes" : "no") << endl; return 0; }- List the variables to remove or rename and explain why.
- Refactor so each variable has a clear purpose and is used > 1 time (or make it
constif it shouldn’t change).
-
Name for intent
Improve the identifiers so the code reads like its intention (no comments needed).int x = 40; int y = 60; int z = x + y; double a1 = z / 2.0; // average? bool f = a1 > 50.0; // pass flag?- Rename variables to communicate meaning (e.g.,
leftScore,rightScore,sum,average,passed). - Mark values that shouldn’t change as
const.
- Rename variables to communicate meaning (e.g.,
-
Tighten scope & add
const
Move declarations as close as possible to first use, and mark read-only variables asconst.double average; int n; cin >> n; int sum = 0; for (int i = 0; i < n; ++i) { int v; cin >> v; sum += v; } average = sum / static_cast<double>(n); cout << "Average: " << average << "\n";- Which variables can be
const? - Where can you narrow scope without hurting readability?
- What edge case must you guard (hint: value of
n)?
- Which variables can be
-
Debug by watching state transitions
The following program occasionally prints the wrong “max”. Identify the bug and the exact variable transition where it happens. Fix it.#include <iostream> using namespace std; int main() { int maxVal; // <-- uninitialised int count; cin >> count; for (int i = 0; i < count; ++i) { int val; cin >> val; if (val > maxVal) // compare against garbage on first iteration maxVal = val; } cout << "Max: " << maxVal << "\n"; }- Propose two safe initialisation strategies for
maxVal(think: first element vs sentinel). - List a minimal “watch list” of variables for the debugger, with why each matters.
- Propose two safe initialisation strategies for
-
Edge cases & guards
Add robust checks so the program behaves predictably for:n == 0(no inputs): what should “average” be or what should the program report?- Negative inputs if they’re not expected: how should the program react?
Programming Insight (AI) — Variable Coach
- Redundancy scan: “List variables assigned once or never read; suggest removals or merges.”
- Naming pass: “Propose clearer identifiers; explain each rename in terms of intent.”
- Scope/const pass: “Move declarations to first use and mark immutable variables
const.” - Debug plan: “Generate a watch list and expected state transitions for each loop iteration.”
Lesson 2 extension · a variable must represent something
What does the variable remember?
I can recognise int score{120}; as a variable declaration. That is useful, but it is not yet an understanding of the program. What does 120 mean? Which values are permitted? Who may change it, and which later decision will use it? A score, a frame number and a temperature could all be represented by an int, yet confusing one for another would still produce an inappropriate program. The declaration gives the compiler a type. The program must give the value a meaning.
This becomes particularly important when the source was written by somebody else or generated by a system. A plausible identifier and a type do not establish that the state model is correct. I still need to know where the value came from, which operations may change it, what must remain true after each change, and what will fail if the value is wrong. A variable remembers part of the program's state. It also creates a responsibility to account for every permitted transition.
A name is not the value
Consider int score{120};. Four related things are present, and muddling them produces muddled explanations. score is the identifier used in this scope. The type is int. An object of that type exists during execution, and the value currently held by that object is 120. A later assignment may replace 120 while the identifier and type remain unchanged. Therefore, saying that “the variable is 120” is convenient shorthand, but a programmer must be able to give the more precise account when correctness depends upon it.
| Question | Answer for int score{120}; | Why it matters |
|---|---|---|
| What is it called here? | score | The identifier lets source code refer to the object. |
| What operations are permitted? | Those available for int | The type constrains representation and valid operations. |
| What exists during execution? | An integer object | The object has storage and a lifetime. |
| What does it currently hold? | 120 | The value is the state observed at this point in execution. |
Initialisation supplies the first value as the object begins its lifetime. Assignment replaces the value of an object that already exists. Both examples contain an equals sign, which makes them look more alike than they are. Read the complete statement:
int score{120}; // definition and initialisation
score = 145; // assignment to an existing object
A local fundamental object written as int score; has no initial value supplied by the programmer. In C++20, reading that indeterminate value can produce undefined behaviour. The useful rule is straightforward: give a local variable a meaningful first value at its definition. Hoping that every possible route through the program performs an assignment before the first read is not a substitute for establishing the initial state.
Follow the value through the program
The following program contains little syntax, but it has a complete state history. For each assignment, first evaluate the expression on the right using the current values; only then replace the object named on the left. Read the assignment as a transition, not as an isolated line:
#include <iostream>
int main() {
int score{120};
const int collectedPoints{25};
score = score + collectedPoints;
std::cout << "After collection: " << score << '\n';
const int penalty{10};
score = score - penalty;
std::cout << "After penalty: " << score << '\n';
}
| Point reached | score before | Operation | score after |
|---|---|---|---|
| Initialisation | No score object | Begin with 120 | 120 |
| Collection | 120 | Add 25 | 145 |
| Penalty | 145 | Subtract 10 | 135 |
The two constants provide inputs to the transitions. Marking them const records that these objects are not intended to change after initialisation. The mutable object is reserved for the state that genuinely evolves. This tells the reader more than declaring everything mutable merely because the language permits it.
The expected output is After collection: 145, followed by After penalty: 135. What does that result prove? It supports the trace for these starting values and these two operations. It does not prove that a negative collection is rejected, that overflow is impossible or that every caller changes the score appropriately. If the output differs, the table gives us exact states to compare. Debugging becomes the search for the first transition at which the observed state leaves the required trace.
Who is allowed to change it?
A calculation does not need to be complicated to create a difficult state error. It is enough to let too many parts of the program change the same value. If five functions may alter a score, I must understand five sets of assumptions and every ordering in which those functions can run. Narrow scope, a meaningful identifier and a clear owner reduce the number of places in which the state can become wrong.
The earlier suggestion that a variable assigned only once is necessarily redundant must therefore be used as a question, not a verdict. A named intermediate value may expose meaning, prevent repeated work or make a debugger trace easier to understand. Conversely, a value assigned many times may reveal confused responsibility. Count assignments if the count points towards something worth inspecting. Decide by asking whether the object represents a necessary value and whether its permitted changes are clear.
Suppose the score starts at zero and the player collects ten points. I put a breakpoint on the assignment and watch the value become ten. Next I send an event that ought to be rejected. Does the score stay at ten? I then inspect the rendering function. It needs to read the score, but what requirement would justify letting it change the score? This investigation identifies the owner, the permitted transitions and the point at which a lower bound must be enforced. If the answer requires a hunt through five unrelated functions, responsibility for the state is not clear enough.
Programming Insight (AI): obtain a trace, then challenge it
An AI system can produce a value table quickly. Speed is the benefit; correctness is still the question. I give it a score that starts at zero, an accepted ten-point event and a twenty-point deduction that the requirement says must be refused. What value does it show after each line? I calculate the states from the C++, then observe the same transitions in the debugger. The requirement, the trace and the execution must agree. If they do not, the disagreement is useful: the instruction may be incomplete, the table may be wrong, or the program may not implement the requirement. The generated trace helps only when it gives me something precise to verify.
Test the state model
Take one short function from the exercises in this lesson. Choose a variable that changes and write down its intended meaning, initial value, permitted changes and required range. Predict its value after each statement before running the program. Then observe the same value in the debugger and locate the first difference, if one appears. Finally, identify one part of the program that may read the value but has no reason to change it. This is more demanding than recognising the declaration, which is precisely why it teaches more.
Reveal answer
I shall use the score example from this extension and follow score. It represents the player's current score. Its first value is 120, and this example permits two changes: add the collected 25 points, then subtract the 10-point penalty.
| Statement reached | Relevant state | score afterwards |
|---|---|---|
int score{120}; | The score object begins its lifetime with its first value. | 120 |
const int collectedPoints{25}; | A separate read-only input is established. It does not change score. | 120 |
score = score + collectedPoints; | The old score, 120, is read; 25 is added; the result replaces the old value. | 145 |
| The first output statement | The stream reads and reports the score. It does not own or change it. | 145 |
const int penalty{10}; | A second read-only input is established. | 145 |
score = score - penalty; | The old score, 145, is read; 10 is subtracted; the result replaces the old value. | 135 |
| The second output statement | The stream reports the final score without changing it. | 135 |
A debugger should therefore show the sequence 120, 145 and 135. If it does not, the useful observation is the first statement after which the watched value differs from this trace. That is where I would compare the current value, the input to the transition and the operation actually executed.
The source does not state a complete permitted range, so the model answer must supply one rather than pretend that the type decided it. For this exercise I require 0 <= score && score <= 1'000'000. An event whose result would leave that range is rejected and score remains unchanged. C++ also requires the arithmetic result to remain representable by int; a different game may choose a different upper bound, but it must state and enforce that decision just as plainly.
The output statements, and a rendering component in a larger program, have reason to read the score. They have no reason to change it. Reporting state is not ownership of that state.
A variable should leave the reader able to say what is remembered, why it exists, who may change it and what must remain true. That account scales from a five-line example to a large engine. The number of variables grows. The responsibility to explain the state does not disappear with the size of the program.