C Basics · beginner · ~10 min
- 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.
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).
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:
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.%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.
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?
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?
&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?
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?
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:
: in "%d:%d") must appear literally in the input or the conversion stops there.% and the letter: %99s, %3d.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.
Also like printf, scanf does almost no error checking. This makes it a common source of bugs.
Follow two rules:
scanf returns the number of fields it successfully parsed. Compare it against the number you expected.fgets for reading lines. scanf("%s", ...) has no length limit and can overflow your buffer. fgets lets you cap the input length.#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.
Walking through the example with the input 4 2 followed by Ada:
#include <stdio.h> brings in scanf, printf, and fprintf.int a, b; reserves storage for two integers (currently uninitialized garbage).printf("Enter two integers: "); shows the prompt; no newline, so the cursor stays on the same line.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.if (got != 2) is false (got is 2), so we skip the error branch.printf("Sum: %d\n", a + b); prints Sum: 6.char name[100]; reserves a 100-byte buffer.scanf("%99s", name) skips the leftover \n, reads Ada (stopping at the next newline), stores Ada\0 in name, and returns 1.!= 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.
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.)
Compiler errors / warnings (compile with -Wall -Wextra):
&.Runtime / logic symptoms and what they mean:
scanf entirely. Almost always the leftover-newline trap with %c (or fgets after scanf). Skip whitespace with " %c", or drain the buffer.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.Concrete debugging steps:
printf("scanf returned %d\n", got); to see whether the read succeeded.echo "4 2" | ./prog makes the test deterministic and repeatable.scanf bugs are leftover-character bugs.scanf is a frequent source of undefined behavior in C, so treat it carefully:
%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.& (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.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.%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.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.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:
& for scalar targets; never for array (%s) targets.%s with a field width.%lf for double, %d for int).Advanced habits:
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.scanf, drain the rest of the bad line before retrying so you do not spin on the same characters.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.
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 &.%d (int), %lf (double), %f (float), %c (single char, no whitespace skip), %s (one word, skips whitespace).EOF). Never compute with values from a failed read.%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.&, 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.