Introduction
Decision Making is lesson 47 in the C Language Beginner pathway. Decision-making statements choose which code executes according to runtime conditions, allowing a program to validate input, classify data, and respond to state.
This lesson develops the idea from first principles, connects it to the C compilation and execution model, and shows how to test it with small programs. Work through the examples in a real compiler instead of reading them passively. Type the code, compile with warnings enabled, predict the result before running it, and then change one detail at a time. The practice section includes explanation questions, code-reading tasks, debugging work, and small programming exercises so that knowledge can be demonstrated rather than memorized.
Explanation
Learning goals
By the end of this lesson, you should be able to define Decision Making in your own words, identify it in a C program, explain why it affects correctness, and use or evaluate it in a small compilable example. You should also be able to name at least two mistakes associated with the topic, select useful compiler warnings, design boundary tests, and connect the idea to nearby beginner concepts. The aim is not to memorize isolated syntax. The aim is to build a mental model that still works when variable names, input values, compiler versions, or program requirements change.
Core idea and purpose
Decision-making statements choose which code executes according to runtime conditions, allowing a program to validate input, classify data, and respond to state. This selection topic translates business rules and classifications into mutually exclusive or nested execution paths. The clearest structure makes every possible outcome easy to identify. This makes Decision Making part of a wider programming discipline: the source must communicate intent to people, satisfy the grammar and semantic rules of C, translate cleanly, and behave predictably for its complete input range.
When learning Decision Making, separate three questions. First, what does the source text mean according to C? Second, what diagnostics or machine code may an implementation produce? Third, what result can the running program observe? Those questions are related but not interchangeable. A compiler accepting a program does not prove that the program is logically correct, portable, or safe. Similarly, a program producing the expected answer once does not prove that all valid and invalid inputs have been handled.
The most useful vocabulary for this lesson includes condition, branch, truth value, case label, fallthrough, boundary. Say each term aloud, give it a short definition, and point to an example in code. Precise vocabulary helps distinguish a spelling mistake from a type mismatch, a translation problem from a runtime problem, and a correct result from accidental behavior.
Mental model
A scalar condition is evaluated and interpreted as false when zero or true otherwise. The selected statement executes, and control then continues according to the surrounding construct. Apply that model directly to Decision Making. Identify the relevant source construct, determine the types and objects involved, state when evaluation happens, and describe the observable effect. If a rule seems abstract, reduce it to a program with one input and one printed result.
A helpful beginner technique is to trace state on paper. Create columns for the current statement, important object values, the condition or operation being evaluated, and the output produced. For source-level topics, mark the tokens and grammatical roles instead. For toolchain topics, draw the files created at each translation stage. This external record slows the process enough to reveal assumptions and becomes a repeatable debugging method.
Do not build the mental model from one compiler accident. C deliberately permits implementation choices, and some erroneous programs appear to work until an optimization, input, platform, or surrounding statement changes. Prefer guarantees from the language and library contracts, then treat compiler documentation as the source for implementation-specific behavior.
Rules, syntax, and disciplined use
State conditions positively when practical, use braces consistently, order overlapping cases deliberately, provide a sensible default path, and avoid repeating expensive or state-changing expressions. For Decision Making, start by locating the smallest complete construct that expresses the idea. Check its delimiters, required types, lifetime, range, and return behavior. Then ask what must already be true before the construct is used and what becomes true after it completes.
Readable C makes important constraints visible. Use braces around controlled statements, meaningful identifiers, constants instead of unexplained numbers, and whitespace that shows grouping. Keep expressions short when they contain side effects. Initialize objects before reading them. Check library and system return values. When an interface receives an array or pointer, carry the capacity or element count with it. These habits are relevant even when the current lesson focuses on a much smaller piece of syntax.
Compile beginner examples with a command similar to:
cc -std=c17 -Wall -Wextra -Wpedantic lesson.c -o lesson
The exact compiler command varies by platform, but the principles remain stable: request a known language version, enable useful diagnostics, name the source explicitly, and choose a predictable output file. Treat warnings as defects to understand. Do not silence a warning with a cast or option until you can state why the operation is valid.
Worked example 1
The following complete program provides a concrete setting for Decision Making. Read it once without running it. Mark the declarations, expressions, decisions, calls, or translation details connected to the lesson. Then predict every output line and the final exit status.
#include <stdio.h>
int main(void)
{
int score = 74;
if (score < 0 || score > 100) {
puts("Invalid score");
} else if (score >= 80) {
puts("Distinction");
} else if (score >= 50) {
puts("Pass");
} else {
puts("Needs more practice");
}
return 0;
}
Start the walkthrough at the include directives. A header makes library declarations available so calls can be type-checked. Next identify file-scope declarations and function definitions. Inside main, follow statements in execution order unless a decision, loop, call, or control transfer changes that order. For every expression connected to Decision Making, write the operand types and values before calculating the result. When output is formatted, match each conversion specification to the corresponding argument.
Compile and run the example. If the result differs from the prediction, do not immediately edit the code. First explain the difference using a rule, type, or state change. Then create a smaller experiment that separates competing explanations. This method turns a surprise into durable knowledge instead of a guessed patch.
Change one constant, input, operator, declaration, or boundary that is central to Decision Making. Predict again, rebuild, and compare. A second build is important because editing source does not change an executable that has not been recompiled. Finally, introduce one deliberate error, read the first compiler diagnostic, restore the valid program, and confirm that the warning or error disappears.
Worked example 2: validation and boundaries
The second program emphasizes validation, a clear result, and testable boundaries. Even if its main construct is familiar, review it through the lens of Decision Making: where is the concept represented, what assumptions are made, and which input would challenge those assumptions?
#include <stdio.h>
int main(void)
{
int value = -7;
int magnitude = value < 0 ? -value : value;
int is_even = magnitude % 2 == 0;
printf("magnitude=%d, %s
", magnitude, is_even ? "even" : "odd");
return 0;
}
Run this example with a normal value, a boundary value, and an invalid or unexpected value when input is present. For a program with fixed data, edit the data to create those cases. Record the expected output before each run. This simple test table is more reliable than trying random values and deciding afterward whether the output looks reasonable.
Next refactor one calculation or decision into a small function. Give the function a precise name, declare it before use, keep its inputs explicit, and return either the result or a status. The refactoring demonstrates that Decision Making participates in program design, not just isolated syntax. Compare the original and refactored outputs to make sure the structural improvement did not change behavior.
Compilation, testing, and debugging workflow
Derive tests from decision boundaries. Include one input on each side of a threshold, equality at the threshold, every explicit category, and an unexpected value for the fallback path. Use a clean loop: state a hypothesis, write the smallest test, compile, run, observe, and explain. Save successful and failing cases so a later change can be checked against them.
Compiler warnings are one layer of evidence. Add sanitizers when the toolchain supports them, for example -fsanitize=address,undefined during a development build. AddressSanitizer can expose many invalid memory accesses, while UndefinedBehaviorSanitizer can identify several operations for which C imposes no defined result. These tools do not prove correctness, and they cannot test paths that never execute, but they make many beginner mistakes much easier to locate.
Use a debugger when printed tracing is insufficient. Set a breakpoint before the construct related to Decision Making, inspect relevant variables, execute one source line at a time, and watch how state changes. Avoid adding so many print statements that timing or output obscures the original issue. Whether using a debugger or logging, return to a written explanation once the cause is understood.
A good test plan contains categories rather than a single example: ordinary inputs, minimum and maximum valid inputs, values just outside a valid range, empty input where meaningful, repeated execution, and resource or conversion failure. For every category, state the expected result and how the program will report failure. This discipline scales from a five-line beginner program to production software.
Common mistakes and how to correct them
1. Writing = where == was intended. For Decision Making, isolate the smallest expression or statement that demonstrates the problem, compile with warnings, and compare the actual result with a written prediction.
2. Creating an unreachable later branch. For Decision Making, isolate the smallest expression or statement that demonstrates the problem, compile with warnings, and compare the actual result with a written prediction.
3. Misleading indentation without braces. For Decision Making, isolate the smallest expression or statement that demonstrates the problem, compile with warnings, and compare the actual result with a written prediction.
4. Forgetting break in a switch. For Decision Making, isolate the smallest expression or statement that demonstrates the problem, compile with warnings, and compare the actual result with a written prediction.
5. Failing to handle invalid input. For Decision Making, isolate the smallest expression or statement that demonstrates the problem, compile with warnings, and compare the actual result with a written prediction.
Many C defects come from combining several correct-looking operations without checking the contract between them. An index calculation may be correct while the array length is wrong. A format string may look right while the argument has a different type. A branch condition may be valid while earlier input conversion failed. Debug from the first violated assumption, not from the final surprising output.
Another mistake is relying on a successful run as proof. Uninitialized values, out-of-bounds access, invalid shifts, signed overflow, or mismatched variadic arguments can produce plausible output. Turn warnings up, simplify the program, test boundaries, and use implementation tools. If the language does not define the behavior, repeatability on one computer does not make it safe.
Best practices for maintainable C
Use Decision Making in a way that makes intent obvious to the next reader. Choose names that describe the role of data, keep scopes as small as practical, and separate input, computation, and output. Prefer one source of truth for sizes and limits. Write functions with clear preconditions and results. When failure is possible, return or record enough information for the caller to respond.
Portability begins with avoiding unjustified assumptions. Do not assume exact primitive sizes, character signedness, evaluation order, byte order, or compiler-specific extensions unless the program intentionally targets a documented environment. Use standard headers for type limits and declarations. Make conversions explicit in the design, not merely through casts. Preserve const qualification when data should not be modified.
Security at beginner level means respecting bounds, validating external data, checking arithmetic before it controls allocation or indexing, and refusing to continue after an essential operation fails. Performance comes later than correct behavior, but clear loops, appropriate types, and avoiding needless work usually support both. Measure before optimizing and keep a correct reference version for comparison.
Connections to nearby topics
Decision Making does not stand alone. It relies on earlier lessons about translation, syntax, types, objects, expressions, and control flow, and it prepares the learner for later work with arrays, strings, functions, pointers, files, and dynamic memory. Draw two arrows backward to prerequisites and two arrows forward to topics that will use this idea. Explain each arrow in one sentence.
The strongest connection is often a type-and-lifetime connection. Ask what objects exist, which expressions can reach them, which operations their types allow, and whether they remain alive for the complete use. The next connection is control flow: ask what executes, how often, and under which condition. The final connection is interface design: ask how data enters the construct and how success or failure leaves it.
Practice questions
1. Define Decision Making without copying the lesson wording. Include its purpose and one consequence for a running program.
2. Locate the part of worked example 1 that most directly demonstrates Decision Making. Explain the relevant tokens and types.
3. Predict the complete output of worked example 1. Then run it and account for every difference.
4. Modify one boundary in the example. Write the expected output before compiling the change.
5. Introduce a realistic mistake related to Decision Making. Record the compiler diagnostic or runtime symptom, then repair it.
6. Write a program of no more than thirty lines that demonstrates Decision Making with one normal case and one boundary case.
7. Add validation so the program rejects one invalid case with a clear message and nonzero exit status.
8. Explain how Decision Making interacts with data types, scope, and control flow in your program.
9. Identify one portability assumption that a careless solution might make. Replace it with a standard or documented approach.
10. Design five test cases in a table with input, expected output, reason for the case, and actual result.
11. Refactor repeated logic into a small function with a prototype. State its preconditions and return contract.
12. Review the program with -Wall -Wextra -Wpedantic. Explain every diagnostic, including why a warning-free build is not a proof of correctness.
13. If a sanitizer is available, run the program with AddressSanitizer and UndefinedBehaviorSanitizer. Report what each tool checks.
14. Compare Decision Making with the most closely related concept in this course. Give one situation where each is clearer.
15. Teach the concept to another beginner using a diagram, trace table, or three-minute verbal explanation, then revise any unclear step.
Practical lab challenge
Build a small command-line program whose central learning objective is Decision Making. Begin with a written requirement, valid input range, expected output, and failure policy. Divide the implementation into input, calculation, and presentation steps even if they remain in one source file. Compile after each working increment and keep the last successful version available for comparison.
Your submission should include the source, compiler command, at least eight test cases, expected and actual results, and a short explanation of one bug found during development. Add comments only where they explain a decision or constraint that the code does not already express. Finish by asking whether another person could change the valid range or output format without rewriting unrelated logic.
For an extension, turn one constant into validated user input, move one responsibility into a function, and add a test at the largest supported boundary. If the extension exposes an assumption in the first design, document the assumption and improve the interface rather than patching only the failing line.
Review checklist
Before marking Decision Making complete, confirm that you can explain the core rule, type the primary syntax without copying, trace both examples, recognize the listed mistakes, and solve at least three practice tasks from a blank file. Confirm that every example compiles with warnings enabled, every external operation is checked, array and string bounds remain valid, and the result is tested rather than assumed.
Finally, revisit the definition at the top of the lesson. Add one sentence about representation, one about control flow, and one about testing. If those sentences are precise and supported by an example you can explain, the topic has moved from recognition toward usable skill.
Summary
Decision Making is now connected to the broader C programming model rather than treated as an isolated definition. Decision-making statements choose which code executes according to runtime conditions, allowing a program to validate input, classify data, and respond to state. A sound solution identifies the relevant source construct, types, objects, control path, preconditions, result, and failure behavior. The two worked examples demonstrate how to translate that reasoning into a complete program, compile it with warnings, predict its output, and test boundaries.
Mastery requires active practice. Rebuild the examples, make controlled changes, diagnose an intentional error, and complete the lab with a written test table. Keep code readable, validate external data, respect bounds and lifetimes, and prefer guarantees from the C standard and library contracts over behavior that merely appears to work on one machine. These habits prepare the learner to use Decision Making safely in larger programs.
Sources and further reading
- ISO/IEC 9899:2018 — Programming Languages — C (C17).
- GCC Online Documentation — C language options, warnings and diagnostics.
- Clang Documentation — Users Manual, diagnostics and sanitizers.
- SEI CERT C Coding Standard — secure coding guidance for C programs.
Continue learning