Chapter 23 of 24

23. A project ladder

Learn by building in small steps

A project combines ideas from several chapters, so a first attempt can feel overwhelming. A project ladder breaks that work into increasingly challenging programs, letting each new project reuse skills you have already practiced.

  1. Start with a tiny program that has one clear input, one calculation, and one result.
  2. Add one new concept at a time, such as functions, arrays, or file input.
  3. For each milestone, build, test, and explain what changed before adding the next feature.

Finishing a small, reliable version is more useful than starting a large project that never works. The project suggestions below are ordered to grow your skills gradually.

Build these in order. Each project combines topics from earlier chapters.

  1. Hello and greeting program: output, literals, and main.
  2. Unit converter: variables, arithmetic, input, and validation.
  3. Number guessing game: conditions, loops, and random-number basics.
  4. Text statistics tool: strings, arrays, and character classification.
  5. Gradebook: functions, arrays, structures, and summaries.
  6. Command-line calculator: functions, parsing, and error handling.
  7. File-backed contact list: structures, allocation, files, and modules.
  8. Dynamic vector library: pointers, resizing, ownership, and tests.
  9. Hash table or linked list: data structures, invariants, and memory
    management.
  10. Small systems utility: command-line arguments, robust I/O, build
    automation, tests, and platform-specific documentation.

For each project, define what valid input looks like, what errors can occur,
how memory is owned, and how you will test it.

A project workflow

For each project, work in small milestones:

  1. Write down what the program must do and a few example inputs and outputs.
  2. Break the work into functions or modules with clear responsibilities.
  3. Implement the smallest working path first.
  4. Compile with warnings and test the normal case.
  5. Add boundary and invalid-input handling.
  6. If the program owns resources, test cleanup on both success and failure.
  7. Refactor names and repeated logic only after behavior is covered by tests.
  8. Document how someone else can build and use it.

Do not begin with a large, complicated design. For example, start a contact
list as a fixed-size in-memory array, then add dynamic resizing, and only then
add file persistence. This lets you learn one new source of complexity at a
time.

#include <assert.h>

int square(int value)
{
    return value * value;
}

int main(void)
{
    assert(square(0) == 0);
    assert(square(3) == 9);
    assert(square(-2) == 4);
    return 0;
}

These are repeatable checks for normal and boundary-like inputs. As programs
grow, move tests into separate test files and avoid relying on assertions for
validating untrusted data.

Checkpoint: Choose the next project on the ladder and write a one-page
specification containing its features, invalid inputs, and three tests.


Suggested topics

0% read