c-foundations Lesson 10 18 min read

How Do I Handle User Input Robustly?

Buffer clearing, input validation loops, and formatting output for Lab 4

Reading: C Text: Ch. 2 §6 (printf/scanf), Ch. 5 §6 (Loop Design Patterns), Ch. 6 (Functions for reusable input handlers)

After this lesson, you will be able to:

  • Write a reusable input validation function using do-while and scanf return checking
  • Check scanf’s return value to detect non-numeric input
  • Use buffer clearing after scanf to prevent leftover character issues
  • Build a menu-driven program that validates user choices
  • Use printf field width and precision specifiers for aligned output

When Users Type “abc” Instead of a Number

Your program expects an integer. The user types “hello”. scanf fails silently, the variable keeps its garbage value, and your program produces nonsense — or crashes.

Defensive input handling isn’t glamorous, but it’s the difference between a program that works in the lab and one that works in the real world. And since Lab 4 (the water bill calculator) requires reading multiple inputs and formatting currency, you need these patterns now.


Defensive Input Patterns

The Buffer Problem (Revisited)

Recall from Lesson 2.5: when you type 42 and press Enter, scanf("%d") reads 42 but leaves \n in the buffer. The standard fix:

scanf("%d", &value);
while (fgetc(stdin) != '\n') {}    // Consume everything up to newline

Use this after every scanf call for numeric types when you’ll read more input later.

Input Validation with do-while

The standard pattern for validated input:

int get_positive_int(const char *prompt)
{
    int value;
    int result;

    do
    {
        printf("%s", prompt);
        result = scanf("%d", &value);
        while (fgetc(stdin) != '\n') {}

        if (result != 1 || value <= 0)
        {
            printf("Invalid input. Please enter a positive integer.\n");
        }
    } while (result != 1 || value <= 0);

    return value;
}

This function:

  1. Prompts the user
  2. Reads input and checks scanf’s return value
  3. Clears the buffer
  4. Validates the value
  5. Repeats if invalid
Check Your Understanding
A user types abc when your program calls scanf("%d", &value). What does scanf return?
A 0 — no items were successfully read
B 1 — it read one item (the letter 'a' as its ASCII value)
C -1 — indicating an error
D The program crashes before scanf returns
Answer: A. scanf returns the number of items successfully matched and assigned. Since "abc" can't be read as %d, zero items were read. The characters stay in the input buffer (which is why you need to clear it), and value keeps whatever it had before — potentially garbage.
Why does this matter?

Every lab that reads user input needs this pattern. If you don’t check scanf’s return value, your program silently uses garbage data when the user types something unexpected. That’s how you get mysterious wrong answers that “work on my machine.”

Labs often require menu-driven interfaces:

char get_menu_choice(void)
{
    char choice;

    do
    {
        printf("\nMenu:\n");
        printf("  A) Add\n");
        printf("  S) Subtract\n");
        printf("  Q) Quit\n");
        printf("Choice: ");
        scanf(" %c", &choice);
        while (fgetc(stdin) != '\n') {}
        choice = toupper(choice);

        if (choice != 'A' && choice != 'S' && choice != 'Q')
        {
            printf("Invalid choice!\n");
        }
    } while (choice != 'A' && choice != 'S' && choice != 'Q');

    return choice;
}

Note the space before %c in scanf(" %c", &choice) — it skips whitespace, including leftover newlines.

Formatted Output for Labs

Lab 4 requires formatted currency output. Here’s how:

Field width for alignment:

printf("%-20s %10.2f\n", "Water charge:", 45.67);
printf("%-20s %10.2f\n", "Sewer charge:", 23.45);
printf("%-20s %10.2f\n", "Total:", 69.12);
Water charge:             45.67
Sewer charge:             23.45
Total:                    69.12

Currency with commas (using setlocale):

#include <locale.h>

int main(void)
{
    setlocale(LC_ALL, "en_US.utf8");
    printf("Total: $%'.2f\n", 12345.67);
    return 0;
}
Total: $12,345.67

The Trick: To format currency with commas in C: (1) include <locale.h>, (2) call setlocale(LC_ALL, "en_US.utf8") at program start, (3) use %'.2f (note the apostrophe) in your format string.

Check Your Understanding
Why is there a space before %c in scanf(" %c", &choice)?
A It's a formatting convention — the space has no effect
B It tells scanf to read a space character before the actual input
C It tells scanf to skip any whitespace (including leftover newlines) before reading the character
D It prevents buffer overflow by adding padding
Answer: C. Without the space, scanf("%c") would read the \n left in the buffer from a previous input — and your program would skip right past the user's next input. The space in the format string tells scanf to consume any whitespace characters first, then read the actual character the user typed.

Testing Your Input Handling

Sanity Check: Test with three categories of input: normal (expected values), boundary (0, maximum, minimum), and edge (negative numbers, letters, empty input, very large numbers). If your program handles all three, it’s solid.

Check Your Understanding
What happens if you skip the buffer-clearing line while (fgetc(stdin) != '\n') {} after a scanf("%d") call, then immediately call scanf("%d") again?
A The second scanf always works fine — it skips whitespace automatically for %d
B The program crashes due to buffer overflow
C It works for valid numbers, but if the user typed 42abc, the leftover abc causes the next scanf to fail
D The second scanf reads the newline as the number 10
Answer: C. scanf("%d") does skip leading whitespace (so a lone \n isn't the problem — that's why option A is tempting). The real issue is leftover non-numeric characters. If the user types 42abc, scanf reads 42 and leaves abc\n in the buffer. The next scanf("%d") immediately hits abc, fails, and returns 0. The buffer-clearing loop prevents this.
Quick Check: Why check scanf's return value?

scanf returns the number of items successfully read. If the user types “abc” when you expect a number, scanf returns 0 (no items read) and the variable keeps its previous value (potentially garbage). Checking the return value lets you detect and handle invalid input.

Quick Check: What does while (fgetc(stdin) != '\n') {} do?

It reads and discards characters from the input buffer one at a time until it reaches the newline character. This clears any leftover input (including the newline from pressing Enter) that could interfere with the next scanf call.

Quick Check: Why use a space before %c in scanf(" %c", &ch)?

The space tells scanf to skip whitespace characters (spaces, tabs, newlines) before reading the character. Without it, scanf("%c") might read a leftover newline from a previous input instead of waiting for the user’s actual input.


Input Handling Is Professional Discipline

Every professional C program handles input defensively. The patterns here — buffer clearing, return value checking, validation loops — are the same patterns used in production C code. They’re not exciting, but they prevent crashes and undefined behavior.

With Week 4 complete, you can write C programs with variables, I/O, operators, control flow, functions, and solid input handling. Next comes code organization — header files and Makefiles that let you split a project across multiple files.

Big Picture: Lab 4 (water bill calculator) combines everything from Weeks 3–4: reading numeric input, computing with tiered rates, validating menu choices, and formatting currency output. The patterns in this lesson are exactly what you need.