C Basics · beginner · ~10 min

Reading input with scanf

- Read integers, floating-point numbers, single characters, and whitespace-delimited words from standard input using `scanf` and its format specifiers. - Explain why `scanf` needs the address-of operator `&` and what would happen without it. - Always check `scanf`'s return value to detect failed or partial reads instead of trusting that input "just worked." - Bound string input with a field width (e.g. `%99s`) or switch to `fgets` so user input can never overflow your buffer. - Recognize and recover from the classic "stuck input" bug where leftover characters in the input buffer break the next read.

Overview

Most useful programs are not silent: they take input from somewhere — a person typing at a keyboard, a file, or another program piping data in. In C, the simplest way to read formatted input from the keyboard (technically, from standard input, abbreviated stdin) is the scanf function from <stdio.h>.

If you have already worked through printf and format specifiers, scanf will feel familiar. printf takes values you have and writes them out as text; scanf reads text in and parses it into values you can use. They even share the same format specifiers — %d for an int, %f for a float, %s for a word, %c for a single character. The big mental shift is the direction of data flow: with printf the data flows out of your variables; with scanf the data flows into your variables, which is exactly why the rules around addresses and & exist.

The word formatted matters. scanf does not hand you raw text; it interprets the text according to a format string. When you write scanf("%d", &n), you are saying "skip any leading whitespace, then read characters that look like a decimal integer, convert them to a real int, and store the result in n." That convenience comes with sharp edges: scanf is famously easy to misuse, and almost every beginner hits the same handful of bugs. This lesson teaches the tool and the discipline that makes it safe.

Key terms used throughout: stdin (the default input stream), format specifier (a %-code describing one value to read), conversion (turning text into a typed value), field width (a number inside a specifier that caps how many characters are read), and return value (how many items scanf successfully converted).

Why it matters

Reading input correctly is the difference between a program that works only when you type perfect data and one that survives real users. People mistype. They paste a word where a number was expected, hit Enter early, or leave a field blank. Files get truncated. Network data arrives malformed.

scanf is also a classic teaching ground for two ideas that follow you through your whole C career:

  • Pointers and addresses. scanf cannot change your variable unless it knows where that variable lives in memory. The & operator is your first real encounter with passing data "by address," which underpins arrays, strings, dynamic memory, and almost every nontrivial C function.
  • Defensive input handling. The unbounded %s conversion is one of the oldest buffer-overflow sources in C. Even though this is not a security lesson, the habit you build here — never read input you cannot bound, and always check that the read succeeded — is the exact habit that prevents real vulnerabilities in production code.

Get scanf right and you have internalized two of the most important reflexes in systems programming.

Core concepts

1. Standard input and the input buffer

Definition. Standard input (stdin) is the default stream a program reads from. When you run a console program, stdin is usually connected to your keyboard, but it can be redirected from a file or another program.

How it works internally. What you type does not reach your program one keystroke at a time. The terminal collects a whole line and only delivers it to your program when you press Enter. Those characters — including the trailing newline \n from Enter — sit in an input buffer. scanf consumes characters from that buffer; anything it does not consume stays behind for the next read. Misunderstanding this leftover-newline behavior causes most scanf confusion.

You type:  4 2 <Enter>

Input buffer (a queue of characters):
  +---+---+---+---+----+
  | 4 | ' '| 2 |'\n'|    |   <- '\n' is the Enter key
  +---+---+---+---+----+
   ^ scanf("%d") reads from the front, leaves the rest

Knowledge check: After scanf("%d", &a) reads the 4 above, what single character is still waiting at the front of the buffer?

2. Format specifiers and conversions

Definition. A format specifier is a %-code in the format string that tells scanf what kind of value to read and where to store it.

Specifier Reads Matching argument type
%d decimal integer int *
%f floating-point float *
%lf floating-point double *
%c one character (no whitespace skip) char *
%s one whitespace-delimited word char[] (no &)
%u unsigned integer unsigned *

How it works. Most specifiers (%d, %f, %s) first skip leading whitespace (spaces, tabs, newlines), then read as many characters as match the conversion, then stop. Two important exceptions: %c does not skip whitespace (it reads the very next character, even a space or newline), and %s stops at the first whitespace — it never reads a multi-word line.

When NOT to use it. Do not use %s for free-form text that may contain spaces (it only grabs the first word) and never use bare %s without a width (overflow risk — see concept 4).

Common pitfall. Using %f with a double variable, or %lf with a float. The specifier must match the exact type, or you get garbage. For reading: float -> %f, double -> %lf.

Knowledge check (find-the-bug): A learner writes double price; scanf("%f", &price); and gets nonsense. What is wrong?

3. The address-of operator &

Definition. &x evaluates to the memory address of the variable x — a pointer to it.

Why scanf needs it. C passes function arguments by value: a function receives a copy. If scanf received a copy of n, it could fill in the copy and you would never see the result. By passing &n, you give scanf the address of the real n, so it can write the parsed value directly into your variable's storage.

Without &  (wrong):           With &  (correct):
  scanf gets a COPY of n        scanf gets n's ADDRESS
  writes into the copy          follows it, writes into n
  your n never changes          your n now holds the value

The exception: arrays — including character arrays used with %s — already decay to the address of their first element, so you write scanf("%99s", name) with no &. Writing &name for a char array is a common and confusing mistake.

Knowledge check (explain in your own words): Why does %d require &n but %s into a char buf[100] does not require &buf?

4. The return value, and bounding input

Definition. scanf returns the number of input items it successfully assigned, or the special value EOF if input ended before any conversion.

Why it matters. This is your only reliable way to know whether the read worked. scanf("%d %d", &a, &b) returns 2 on full success, 1 if only the first number parsed, 0 if the very first item did not match (e.g. the user typed letters), or EOF at end of input. Code that ignores the return value will happily compute with uninitialized garbage.

Bounding strings. Bare %s keeps reading until whitespace with no limit, so a long word writes past the end of your buffer — undefined behavior and a classic overflow. Always add a field width one smaller than the buffer size to leave room for the terminating '\0':

char name[100];           buffer capacity: 100 bytes
scanf("%99s", name);      reads at most 99 chars + 1 for '\0' = 100

When to prefer fgets. If you want a whole line (possibly with spaces), scanf is the wrong tool — use fgets(buf, sizeof buf, stdin), which reads a full line and respects the buffer size.

Knowledge check (predict the output): If the user types hello and the program runs int n; int r = scanf("%d", &n); printf("%d\n", r);, what does it print, and what is the value of n?

Syntax notes

The general shape is one format string plus one address per conversion:

int count = scanf("format", &target1, &target2, ...);

An annotated example:

int   age;
float height;

//        +-- literal space matches ANY run of whitespace in input
//        |
int got = scanf("%d %f", &age, &height);
//                ^   ^    ^      ^
//                |   |    |      address of height (float*)
//                |   |    address of age (int*)
//                |   read a float
//                read an int
// got == 2 means both values were read successfully

Notes on the format string:

  • A space, tab, or newline in the format string matches zero or more whitespace characters in the input — they are interchangeable.
  • Ordinary characters in the format (e.g. the : in "%d:%d") must appear literally in the input or the conversion stops there.
  • A field width goes between % and the letter: %99s, %3d.

Lesson

What scanf does

scanf reads formatted input from stdin. It stores each value through the address of a variable, which is why you must write & before the variable name. The & operator gives the memory address where the value should be written.

Like printf, scanf uses format specifiers such as %d for an integer or %s for a string.

Why scanf needs care

Also like printf, scanf does almost no error checking. This makes it a common source of bugs.

Follow two rules:

  • Always check the return value. scanf returns the number of fields it successfully parsed. Compare it against the number you expected.
  • Prefer fgets for reading lines. scanf("%s", ...) has no length limit and can overflow your buffer. fgets lets you cap the input length.

Code examples

#include <stdio.h>

int main(void) {
    int a, b;

    printf("Enter two integers: ");

    /* scanf returns how many items it converted; we expect exactly 2. */
    int got = scanf("%d %d", &a, &b);
    if (got != 2) {
        fprintf(stderr, "Error: expected two integers.\n");
        return 1;                 /* bail out instead of using garbage */
    }
    printf("Sum: %d\n", a + b);

    /* Now read a single bounded word into a fixed buffer. */
    char name[100];
    /* %99s reads at most 99 chars + the '\0'; note: no & for an array. */
    if (scanf("%99s", name) != 1) {
        fprintf(stderr, "Error: expected a name.\n");
        return 1;
    }
    printf("Hello, %s!\n", name);

    return 0;
}

What it does. It prompts for two integers, reads them with one scanf, and refuses to continue unless both parsed. Then it reads a single word safely into a 100-byte buffer using a width-limited %99s.

Expected output for the input 4 2 then Ada:

Enter two integers: 4 2
Sum: 6
Hello, Ada!

Edge cases. If you type 4 x, got is 1, so the program prints the error and exits. If you type a 200-character word, only 99 characters are stored (the rest stay in the buffer) — no overflow. If input ends immediately (e.g. piped from an empty file), scanf returns EOF, which also fails the != 2 check.

Line by line

Walking through the example with the input 4 2 followed by Ada:

  1. #include <stdio.h> brings in scanf, printf, and fprintf.
  2. int a, b; reserves storage for two integers (currently uninitialized garbage).
  3. printf("Enter two integers: "); shows the prompt; no newline, so the cursor stays on the same line.
  4. scanf("%d %d", &a, &b) skips leading whitespace, parses 4 into a, the space in the format matches the space in the input, parses 2 into b, and returns 2. The \n from Enter is left in the buffer.
  5. if (got != 2) is false (got is 2), so we skip the error branch.
  6. printf("Sum: %d\n", a + b); prints Sum: 6.
  7. char name[100]; reserves a 100-byte buffer.
  8. scanf("%99s", name) skips the leftover \n, reads Ada (stopping at the next newline), stores Ada\0 in name, and returns 1.
  9. The != 1 check is false, so we print Hello, Ada! and return 0.

State trace as input is consumed:

Step Buffer remaining a b name scanf return
before read 1 4 2\nAda\n ? ?
after read 1 \nAda\n 4 2 2
after read 2 \n 4 2 Ada 1

Notice how the leftover \n after read 1 is harmlessly skipped by %99s (because %s skips leading whitespace). That is exactly the kind of leftover that would cause trouble with %c.

Common mistakes

Mistake 1: Forgetting &.

int n;
scanf("%d", n);   /* WRONG: passes n's (garbage) value as an address */

The value of an uninitialized n is treated as a memory address and written to — usually a crash or memory corruption. Fix: scanf("%d", &n);. Prevent it: if your compiler warns about format/argument mismatches (-Wall), it will flag this.

Mistake 2: Unbounded %s.

char buf[8];
scanf("%s", buf);   /* WRONG: a 20-char word overflows buf */

Fix: scanf("%7s", buf); (width = size minus 1 for the '\0'), or use fgets(buf, sizeof buf, stdin);. Recognize it: random crashes that depend on input length are a tell-tale buffer-overflow sign.

Mistake 3: Ignoring the return value.

int age;
scanf("%d", &age);
printf("%d\n", age * 2);   /* if the user typed letters, age is garbage */

Fix: if (scanf("%d", &age) != 1) { /* handle error */ }.

Mistake 4: The leftover-newline trap with %c.

int n; char c;
scanf("%d", &n);
scanf("%c", &c);   /* WRONG: reads the leftover '\n', not the user's char */

Because %c does not skip whitespace, it grabs the newline still sitting in the buffer. Fix: put a space before %c to skip whitespace: scanf(" %c", &c);.

Mistake 5: Wrong specifier for the type.

double d;
scanf("%f", &d);   /* WRONG: %f is for float; use %lf for double */

Fix: scanf("%lf", &d);. (Note the asymmetry with printf, where %f prints both float and double.)

Debugging tips

Compiler errors / warnings (compile with -Wall -Wextra):

  • *"format '%d' expects argument of type 'int ', but argument has type 'int'" — you forgot the &.
  • *"format '%lf' ... but argument has type 'float '" — specifier and variable type disagree.

Runtime / logic symptoms and what they mean:

  • The program seems to skip a scanf entirely. Almost always the leftover-newline trap with %c (or fgets after scanf). Skip whitespace with " %c", or drain the buffer.
  • An infinite loop reading the same bad input. If you loop while (scanf("%d", &n) == 1) and the user types letters, the letters are never consumed, so each iteration re-fails on the same characters. You must clear the offending input (e.g. read and discard up to the next newline) before retrying.
  • Values look like garbage. Either you ignored the return value (the read failed) or you used a mismatched specifier.

Concrete debugging steps:

  1. Print the return value: printf("scanf returned %d\n", got); to see whether the read succeeded.
  2. Print each variable right after reading it, so you know whether the bug is in the read or in later logic.
  3. Reproduce with redirected input: echo "4 2" | ./prog makes the test deterministic and repeatable.
  4. Ask: What is left in the buffer after this read? Most scanf bugs are leftover-character bugs.

Memory safety

scanf is a frequent source of undefined behavior in C, so treat it carefully:

  • Buffer overflow. Bare %s (and %[...]) write an unbounded number of bytes. Always include a field width one less than the buffer size, leaving room for the terminating '\0'. Example: a char buf[64] pairs with %63s.
  • Writing through a bad address. Forgetting & (for scalars) passes a garbage value as a destination address; scanf then writes to wherever that points — corrupting memory or crashing. Conversely, adding & to an array name passes the wrong pointer type.
  • Uninitialized reads. If scanf fails (return value less than expected), your target variables keep their previous, possibly-uninitialized values. Using them is undefined behavior. Guard every use with a return-value check.
  • Null termination. %s always writes a '\0', but only if the buffer had room — which is exactly why the width must be size-minus-one. A buffer that is exactly full of characters with no room for '\0' would not be a valid C string.
  • Robustness habit. When the input format is even slightly untrusted or interactive, prefer reading a whole line with fgets into a sized buffer and then parsing it (e.g. with sscanf or strtol). This separates "get the bytes safely" from "interpret the bytes," which is easier to validate and recover from.

Real-world uses

Where it shows up. scanf and its siblings (fscanf for files, sscanf for in-memory strings) appear in command-line tools, configuration parsers, small interactive utilities, competitive-programming solutions (where input format is fixed and trusted), embedded firmware reading a serial console, and quick data-processing scripts that consume well-defined columnar text.

Best-practice habits:

Beginner rules:

  • Always pass & for scalar targets; never for array (%s) targets.
  • Always bound %s with a field width.
  • Always check the return value before using what was read.
  • Match the specifier to the type exactly (%lf for double, %d for int).

Advanced habits:

  • For interactive or untrusted input, read a full line with fgets into a sized buffer, then parse with sscanf or strtol/strtod. strtol even tells you, via its endptr, exactly where parsing stopped, so you can reject trailing junk.
  • After a failed scanf, drain the rest of the bad line before retrying so you do not spin on the same characters.
  • Keep input parsing in its own small function with a clear contract (returns success/failure), so the rest of the program never touches raw stdin. This mirrors the validation discipline you saw with printf and format specifiers: be deliberate about exactly what format you accept.

Practice tasks

Beginner 1 — Echo a number. Read a single integer with scanf and print You entered: N. Requirement: check the return value and print No number entered. if the read fails. Hint: compare scanf("%d", &n) to 1. Concepts: %d, &, return value.

Beginner 2 — Safe word reader. Declare char word[20] and read one word into it without risking overflow, then print its length using printf("%zu\n", strlen(word)) (include <string.h>). Requirement: use the correct field width for a 20-byte buffer. Hint: the width is the buffer size minus one. Concepts: bounded %s, no & for arrays.

Intermediate 1 — Three-number average. Read three doubles separated by whitespace in a single scanf call and print their average to two decimal places. Requirement: print an error and exit non-zero unless exactly three values were read. Input example: 1.5 2.5 6.0 -> Average: 3.33. Hint: the specifier for double is %lf. Concepts: multiple conversions, return value, %lf.

Intermediate 2 — Date splitter. Read a date typed as YYYY-MM-DD (e.g. 2026-06-27) using literal - characters in the format string, into three ints. Print them as Year=… Month=… Day=…. Requirement: reject input that does not produce all three fields. Hint: the format string is "%d-%d-%d". Concepts: literal characters in the format string, return value.

Challenge — Retry loop. Repeatedly prompt Enter a positive integer: until the user provides one. Your program must (a) reject non-numeric input without falling into an infinite loop, and (b) reject zero or negatives. After a bad read, drain the leftover characters in the input buffer before prompting again. Hint: on a failed scanf, read and discard characters up to and including the next '\n' (or EOF) using getchar() in a small loop. Concepts: return value, input-buffer draining, validation, loops.

Summary

  • scanf reads formatted input from stdin and stores each value through the address of a variable, which is why scalar targets need &. Arrays used with %s already are addresses, so they take no &.
  • Match specifiers to types: %d (int), %lf (double), %f (float), %c (single char, no whitespace skip), %s (one word, skips whitespace).
  • Always check the return value — the count of items converted (or EOF). Never compute with values from a failed read.
  • Bound every %s with a field width one smaller than the buffer (char b[100] -> %99s), or use fgets for whole lines. Unbounded %s is a buffer overflow.
  • The two bugs you will hit most: forgetting &, and the leftover-newline trap with %c (fix with " %c"). When in doubt about leftover characters, read a whole line with fgets and parse it separately.

Practice with these exercises