Input & Output
printf and scanf are the two functions you'll type more than almost any other in early C — but their format-specifier system is stricter and more dangerous than it looks, and worth understanding properly from day one.
A running C program is mostly deaf and mute until it's connected to the outside world — input is how data gets in, and output is how results get out. This page walks through the whole picture in one pass: printf and scanf first, since they're the two functions you'll type constantly, then the smaller building blocks underneath them (getchar, putchar, fgets, puts), the three standard streams every program is handed automatically, what's really happening inside the terminal when text moves in either direction, and the handful of traps — the leftover newline, the unchecked scanf return value, the unbounded %s — that catch almost everyone at least once.
What counts as input
Input is any data that flows from outside a running program into it — most commonly text typed at a keyboard, but it can just as easily come from a file or from another program's output piped in. C reads input using a family of standard functions, and the most common of them is scanf, which parses typed text according to a format you specify and stores the result into your variables.
scanf pauses the program right where it's called, waits for the user to type something and press Enter, converts what was typed into an int, and stores it into age.
#include <stdio.h>
int main(void)
{
int age;
printf("How old are you? ");
scanf("%d", &age);
printf("Got it — you said %d.\n", age);
return 0;
}What counts as output
Output is data flowing the other way — from the running program back out to the world, most often as text printed to the terminal. printf is the workhorse function for this: it builds a string according to a format you give it and sends that string to standard output.
This program produces no input at all — its entire job is to send one line of text out to the terminal it was run from.
#include <stdio.h>
int main(void)
{
printf("Hello, world!\n");
return 0;
}The standard I/O header, stdio.h
Every function this page covers — printf, scanf, getchar, putchar, fgets, puts, fprintf, fscanf, and more — is declared in one header, stdio.h ("standard input/output"). Without including it, the compiler has no declaration for these functions and, depending on the compiler and C standard in use, will either refuse to compile or silently assume the wrong thing about how they should be called.
This single #include line at the top of a file is what unlocks essentially every function on this page — it's easy to forget while experimenting in a small snippet, and the resulting errors ("implicit declaration of function") can look unrelated to the real cause.
#include <stdio.h>printf for basic output
In its simplest form, printf just takes a string and writes it, character for character, to standard output.
printf's real power, though, comes from its format string — a string that can hold plain text mixed with % placeholders standing in for values supplied afterward.
#include <stdio.h>
int main(void)
{
printf("Learning C is easier one function at a time.\n");
return 0;
}Weaving text and variables together with %d
Anywhere a % specifier appears inside the format string, printf substitutes the next argument in the list, converted and formatted according to that specifier. %d is the specifier for a plain signed integer.
Everything in the format string outside of %d prints exactly as written; %d itself is replaced by unreadMessages's value, 7, converted to the digits "7".
#include <stdio.h>
int main(void)
{
int unreadMessages = 7;
printf("You have %d unread messages.\n", unreadMessages);
return 0;
}The newline character and when output actually appears
\n inside a format string is an instruction, not literal text — it tells the output stream to move to the start of a new line. Without it, one printf call's output runs directly into the next one's, on the same line.
This prints Step oneStep twoStep three all glued onto a single line, since only the last call includes a \n.
Standard output is often line-buffered when it's connected to a terminal, meaning printf's text can sit in an internal buffer and not actually reach the screen until a \n triggers a flush (or the program ends). Leaving \n off a printf call doesn't just change the visual layout — on some setups it can genuinely delay when that text becomes visible at all, which matters if the very next line of code is a long-running loop.
#include <stdio.h>
int main(void)
{
printf("Step one");
printf("Step two");
printf("Step three\n");
return 0;
}Escape sequences
A backslash followed by a specific letter inside a string is an escape sequence — a two-character combination that the compiler turns into one special character, rather than printing the backslash itself.
\t lines up the text after it at the next tab stop; \\ is required to print one literal backslash, since a single backslash always begins an escape sequence instead of printing itself; \" lets a double quote appear inside a string that is itself delimited by double quotes.
\n newline
\t horizontal tab
\\ a literal backslash
\" a literal double quote
\' a literal single quote
\r carriage return
\b backspace
\a alert (terminal bell)#include <stdio.h>
int main(void)
{
printf("Name:\tJordan\n");
printf("Path:\tC:\\Users\\Jordan\n");
printf("She said, \"C is fun.\"\n");
return 0;
}The format specifiers, at a glance
Each format specifier tells printf or scanf how to interpret the bytes of the matching argument — as an integer, a character, text, an address, and so on.
This table is worth returning to often in the beginning — the difference between %d and %f, or between %d and %u, matters more than it might seem.
%d, %i signed decimal integer
%u unsigned decimal integer
%c a single character
%f floating-point, fixed notation
%e floating-point, scientific notation
%g floating-point, whichever of %f/%e is shorter
%s a null-terminated string
%x unsigned integer, hexadecimal (lowercase)
%o unsigned integer, octal
%p a pointer's address
%% a literal percent signPrinting floating-point values
%f prints a float or double in ordinary decimal notation. Left unconstrained, it defaults to six digits after the decimal point, whether or not the actual value needs that many.
Adding .2 after the % (and before the f) tells printf to show exactly two digits after the decimal point instead of the default six — the value stored in price never changes, only how it's displayed.
#include <stdio.h>
int main(void)
{
float price = 19.5f;
printf("%f\n", price); // 19.500000
printf("%.2f\n", price); // 19.50
return 0;
}Controlling width and precision
A number placed directly after the % sets a minimum field width; a .N adds precision, whose meaning depends on the specifier it's attached to.
%5d reserves at least 5 character positions, padding with spaces on the left by default; a 0 right after the % switches that padding to zeros instead; on a float, .3 fixes the digits after the decimal point to exactly three, rounding as needed.
#include <stdio.h>
int main(void)
{
printf("[%5d]\n", 42); // [ 42] - padded with spaces to width 5
printf("[%05d]\n", 42); // [00042] - padded with zeros instead
printf("[%.3f]\n", 3.14159); // [3.142] - exactly 3 digits after the point
return 0;
}Printing several values in one call
A single printf call can contain as many % specifiers as it has trailing arguments to match — each specifier consumes the next argument, strictly in the order they were written.
The first specifier, %s, is matched to the first extra argument, name; %d to score; %.1f to gpa — position, not naming, is what links a specifier to its argument.
#include <stdio.h>
int main(void)
{
char name[] = "Priya";
int score = 92;
float gpa = 3.8f;
printf("%s scored %d, GPA %.1f\n", name, score, gpa);
return 0;
}The specifier and the argument must agree
printf has no way to check, at compile time or at run time, that the specifier you wrote actually matches the type of the argument you passed — it simply reads however many bytes that specifier implies off the argument list and interprets them accordingly.
This compiles without complaint on many setups, and still prints nonsense: %d tells printf to read an int's worth of bits and show them as a decimal integer, but the bits it actually finds came from a float, which is represented completely differently.
Mismatching a format specifier and its argument's real type is undefined behavior, not merely 'wrong output' — the safest response is always to fix the specifier, not to guess at what garbage value might come out. Many modern compilers (GCC, Clang) will flag an obvious mismatch like this one with a -Wformat warning, which is worth never silencing.
#include <stdio.h>
int main(void)
{
float temperature = 98.6f;
printf("%d\n", temperature); // wrong specifier for a float argument
return 0;
}scanf for basic input
scanf is printf's mirror image: instead of formatting values out, it parses typed text in, according to a format string built from the same family of % specifiers.
%d here tells scanf to read characters, skipping leading whitespace, until it finds something that no longer looks like part of an integer, and convert what it collected into an int.
#include <stdio.h>
int main(void)
{
int quantity;
scanf("%d", &quantity);
return 0;
}Why scanf needs the & operator
A normal function in C can't reach back into the caller and modify one of the caller's variables — arguments are passed by value, meaning the function only ever receives a copy. scanf gets around this the same way any C function does: by asking for the variable's address instead of its value, using &, so it can write directly into that memory location itself.
&quantity is not the number scanf reads — it's the address of the memory that will hold that number once scanf writes it there.
int quantity;
scanf("%d", &quantity); // &quantity: the location to write into, not a valueA first complete scanf program
quizScore starts out uninitialized — its bits are whatever was already sitting in that memory.
printf shows the prompt, with no trailing newline, so the cursor waits on the same line.
scanf("%d", &quizScore) blocks until Enter is pressed, parses the typed digits, and writes the resulting int directly into quizScore's memory.
The final printf reads quizScore normally and sees the freshly written value immediately.
#include <stdio.h>
int main(void)
{
int quizScore;
printf("Enter your quiz score: ");
scanf("%d", &quizScore);
printf("Recorded: %d\n", quizScore);
return 0;
}Reading multiple values in a single scanf call
One scanf call can read several values at once by chaining specifiers and & arguments — scanf treats any run of spaces, tabs, or newlines the user types between values as an ordinary separator.
Typing 4 2.5 and pressing Enter fills quantity with 4 and unitPrice with 2.5, matched to the specifiers in the order they appear — the literal space between %d and %f in the format string tells scanf to skip whitespace there, it doesn't need to match the input character for character.
#include <stdio.h>
int main(void)
{
int quantity;
float unitPrice;
printf("Quantity and unit price: ");
scanf("%d %f", &quantity, &unitPrice);
printf("Total: %.2f\n", quantity * unitPrice);
return 0;
}Reading a floating-point value: %f versus %lf
printf treats float and double arguments identically, because any float argument passed to a variadic function like printf is automatically promoted to double first — so %f is correct for printing either one. scanf has no such promotion to lean on, because it's handed a pointer, and a pointer to float and a pointer to double are genuinely different sizes and layouts. Reading into a float uses %f; reading into a double requires %lf.
Using %f in scanf where the destination is actually a double is a genuine bug, not just a style slip — scanf will write only 4 bytes' worth of data where 8 are expected, corrupting whatever memory sits right after that double. This is one of the few places where printf and scanf's rules for the 'same' specifier quietly diverge.
#include <stdio.h>
int main(void)
{
float shortMeasurement;
double preciseMeasurement;
scanf("%f", &shortMeasurement); // float destination
scanf("%lf", &preciseMeasurement); // double destination
printf("%f and %f\n", shortMeasurement, preciseMeasurement); // %f is fine for both, here
return 0;
}Reading a single character with %c
%d, %f, and %s all skip any leading whitespace automatically before reading a value. %c is the exception — it reads whatever character comes next, whitespace included, which becomes important the moment a %c read follows a %d or %f read.
The single space written before %c isn't decorative — it tells scanf to consume and discard any whitespace sitting in the input before reading the real character.
A %c read placed right after a %d or %f read is the classic setup for this bug: the earlier read leaves the Enter key's newline unread in the input, and a bare %c immediately grabs that leftover newline instead of waiting for the character the user actually meant to type. Writing " %c" (with the leading space) instead of "%c" is the fix, and it's worth using out of habit any time %c isn't the very first thing a program reads.
#include <stdio.h>
int main(void)
{
char grade;
printf("Enter a single letter grade: ");
scanf(" %c", &grade);
printf("You entered: %c\n", grade);
return 0;
}getchar()
getchar() reads and returns exactly one character from standard input — nothing more. Its return type, notably, is int, not char.
getchar() returns int rather than char specifically so it has room for one extra possible value beyond every real character: EOF, a special negative value signaling that there is no more input to read. If getchar() returned a plain char, there would be no value left over that couldn't also be a legitimate character, making end-of-input impossible to represent reliably.
#include <stdio.h>
int main(void)
{
int ch = getchar();
printf("You typed: %c\n", ch);
return 0;
}putchar()
putchar() is getchar()'s mirror image: it writes a single character to standard output.
Three separate putchar calls together print Hi followed by a newline — putchar is the smallest possible unit of character output, useful whenever output needs to be built up one character at a time.
#include <stdio.h>
int main(void)
{
putchar('H');
putchar('i');
putchar('\n');
return 0;
}Reading a full line with fgets()
scanf("%s", ...) stops the instant it hits whitespace, so it can never capture something like a full name typed with a space in it — it would only grab the first word. fgets() is built specifically to read an entire line, spaces included, up to a size limit you control.
fgets reads from stdin into fullName, stopping at whichever comes first: a newline, or one character short of the given buffer size — which means, unlike scanf("%s", ...), it can never write past the end of the array you gave it.
sizeof(fullName) only works as a correct size limit here because fullName is a genuine array, whose full size the compiler knows at compile time.
fgets keeps the newline character (if one was read and fits) as part of the stored string, right before the terminating '\0'.
strcspn(fullName, "\n") locates that newline's position; overwriting it with '\0' trims it off so the greeting doesn't print an odd blank line.
#include <stdio.h>
#include <string.h>
int main(void)
{
char fullName[50];
printf("Enter your full name: ");
fgets(fullName, sizeof(fullName), stdin);
fullName[strcspn(fullName, "\n")] = '\0';
printf("Hello, %s!\n", fullName);
return 0;
}puts()
puts() writes a string to standard output and then automatically appends a newline of its own — a small convenience printf doesn't offer.
puts adds exactly one \n after the text it's given — you never write \n yourself when calling puts, and doing so would just produce a blank line underneath.
#include <stdio.h>
int main(void)
{
puts("This line gets a newline for free.");
return 0;
}printf versus puts
For output that is nothing but a fixed string, with no values to substitute in, puts is simpler and slightly cheaper than printf, precisely because it doesn't need to scan the string looking for % specifiers at all.
The moment you need to substitute a variable's value into the output, though, puts can't help — that's exactly what printf's format-string mechanism exists for.
printf("Done.\n"); // works, but does more than it needs to
puts("Done."); // simpler for a plain string with no formattingscanf versus fgets, and why fgets often wins
scanf is convenient for cleanly-typed numeric input, but it's a poor fit for anything a user might type carelessly, since a bad token can leave scanf's internal state confused about what to do next. A more robust pattern many real programs use instead is: read one whole line of raw text with fgets, then parse or convert that line's content separately, on your own terms.
fgets always consumes exactly one line no matter what it contains, so the input stream can never get 'stuck' the way it can after a failed scanf; sscanf then does the actual parsing against a string already fully in hand, and its return value tells you plainly whether that parsing succeeded.
#include <stdio.h>
int main(void)
{
char line[100];
int quantity;
printf("Enter a quantity: ");
fgets(line, sizeof(line), stdin);
if (sscanf(line, "%d", &quantity) == 1)
printf("Got %d\n", quantity);
else
printf("That wasn't a valid number.\n");
return 0;
}The three standard streams
Every C program is handed three open streams automatically, before main even runs — you never open them yourself.
By default all three are connected to the terminal a program is run from, which is why stdin appears to be the keyboard and stdout appears to be the screen — but at the operating-system level they can each be redirected independently, to a file or to another program, without the C program's own code changing at all.
stdin standard input - where scanf, getchar, and fgets(..., stdin) read from
stdout standard output - where printf, putchar, and puts write to
stderr standard error - a separate output stream, meant for diagnosticsstderr and fprintf(stderr, ...)
stderr exists specifically so that error and diagnostic messages can be kept separate from a program's normal output, even when that normal output has been redirected elsewhere (into a file, say). Writing to it uses fprintf, giving stderr as the destination stream.
Even if this program's stdout is redirected into a log file (program > out.txt), the warning still shows up on the terminal, because stderr is a genuinely separate stream from stdout, not just a formatting convention.
stderr is typically unbuffered or only lightly buffered, unlike stdout — one practical consequence is that error messages written with fprintf(stderr, ...) tend to appear immediately, even in situations where stdout output is still sitting in a buffer waiting to be flushed.
#include <stdio.h>
int main(void)
{
fprintf(stderr, "Warning: config file not found, using defaults\n");
printf("Program output continues normally.\n");
return 0;
}fprintf() for writing to any stream
fprintf works exactly like printf, except its first argument names which stream to write to — printf itself is really just fprintf with stdout hardcoded as that first argument.
#include <stdio.h>
int main(void)
{
int score = 88;
fprintf(stdout, "Score: %d\n", score); // identical to printf("Score: %d\n", score)
return 0;
}fscanf() for reading from any stream
fscanf is scanf's equivalent generalization — it takes a stream to read from as its first argument, with scanf itself just being fscanf with stdin fixed in that spot.
fscanf and fprintf matter more once file handling is introduced, since a stream opened on an actual file (via fopen) can be passed in that same first argument slot — the same %-specifier system you already know keeps working unchanged.
#include <stdio.h>
int main(void)
{
int age;
fscanf(stdin, "%d", &age); // identical to scanf("%d", &age)
printf("Age: %d\n", age);
return 0;
}What actually happens when printf runs
It helps to have a mental picture of the steps hiding behind one printf call, rather than treating it as unexplainable magic.
printf itself never touches the screen directly — it hands finished text to the stdout stream, and it's the stream (plus whatever it's connected to) that actually gets that text displayed.
your program
|
calls printf("Score: %d\n", 88)
|
printf walks the format string, substituting %d with 88
|
the resulting text "Score: 88\n" is handed to the stdout stream
|
stdout's buffer holds it (possibly briefly)
|
the terminal displays itOutput buffering and fflush
For efficiency, output usually isn't sent to its destination the instant printf is called — it accumulates in an internal buffer first, and gets flushed (actually transmitted) when the buffer fills up, when a newline appears (if the stream is line-buffered), or when the program exits normally.
fflush(stdout) forces whatever is currently sitting in stdout's buffer out to its destination immediately, which matters most for a status message meant to appear before a long delay, not after it.
printf calls -> internal buffer -> flush -> actual destination (terminal, file, ...)#include <stdio.h>
int main(void)
{
printf("Working"); // may sit in the buffer, unflushed, for a moment
fflush(stdout); // force it out to the terminal right now
// ... some long-running work here ...
printf(" done.\n");
return 0;
}Putting it together: one complete I/O program
A single small program exercising several of these pieces at once.
Two entirely different reading strategies live in one program: fgets for text that might contain spaces, and scanf for a single clean number — a pairing that comes up constantly in real command-line programs.
This particular ordering (fgets, then scanf) happens to avoid the leftover-newline problem, because fgets always consumes its trailing newline as part of the line it reads. The next section covers what happens when the order is reversed instead — scanf("%d", ...) followed by something that reads a single character.
#include <stdio.h>
int main(void)
{
char name[30];
int age;
printf("Name: ");
fgets(name, sizeof(name), stdin);
name[strcspn(name, "\n")] = '\0';
printf("Age: ");
scanf("%d", &age);
printf("\n--- Summary ---\n");
printf("Name: %s\n", name);
printf("Age: %d\n", age);
return 0;
}The input buffer and the leftover newline
scanf("%d", ...) reads only the digits that make up the number — it deliberately leaves the newline produced by the user's Enter key sitting unread in the input buffer, waiting for whatever read comes next.
This program appears to skip the second prompt entirely: scanf("%c", &letter) immediately consumes the newline still sitting in the buffer from the first read, so letter ends up holding '\n' rather than whatever the user typed next.
The fix is the same leading-space trick from the %c section earlier: scanf(" %c", &letter) skips any pending whitespace, leftover newline included, before reading the real character. This exact bug — a numeric read followed by a character read — is common enough to be worth checking for by habit.
#include <stdio.h>
int main(void)
{
int count;
char letter;
printf("Enter a count: ");
scanf("%d", &count);
printf("Enter a letter: ");
scanf("%c", &letter); // grabs the leftover \n instead of waiting
printf("count=%d letter=%d\n", count, letter);
return 0;
}Checking scanf's return value
scanf returns an int: the count of values it successfully read and converted. That return value deserves real attention in any program where the input might not be exactly what's expected — it's the only reliable signal that something went wrong.
Ignoring scanf's return value is one of the most common ways bad input silently turns into a badly-behaving program: a and b might be left partly unset, and every calculation downstream then runs on garbage instead of stopping to report the actual problem.
return value meaning
2 both requested values were read and converted
1 only the first value converted; the second failed or wasn't there
0 the very first value failed to convert (input didn't match)
EOF end of input was reached before any value could be read#include <stdio.h>
int main(void)
{
int a, b;
printf("Enter two integers: ");
int result = scanf("%d %d", &a, &b);
if (result == 2)
printf("Sum: %d\n", a + b);
else if (result == EOF)
printf("Input ended unexpectedly.\n");
else
printf("Invalid input — expected two integers.\n");
return 0;
}Why scanf("%s", ...) is dangerous
%s in scanf reads characters into a char array until it hits whitespace — and by default, it does so with absolutely no awareness of how large that array actually is.
If the array is 10 bytes and the user types 30 characters, scanf happily writes all 30 (plus a terminating '\0') straight past the end of the array — a classic buffer overflow, and a genuine security hole in real software, not just a theoretical concern.
Two real defenses exist. One is to give %s an explicit width limit matching the buffer, one less than its size to leave room for the terminator — scanf("%9s", name) for a 10-byte array. The other, generally the safer default for reading text at all, is to use fgets(name, sizeof(name), stdin) instead, which enforces its size limit unconditionally and can never be told the wrong number by accident.
char name[10];
scanf("%s", name); // no & here — an array name already decays to an address
// typing more than 9 characters overflows name, corrupting whatever memory follows itfgets and the newline it keeps
Unlike scanf, fgets stores the newline character as part of the string it reads, right before the terminating '\0' — but only if that newline was actually reached before running out of buffer space.
Checking line[len - 1] == '\n' before removing it matters, because if the typed line was longer than the buffer could hold, fgets never reached a newline at all, and blindly chopping the last character would cut off real data instead.
#include <stdio.h>
#include <string.h>
char line[20];
fgets(line, sizeof(line), stdin);
size_t len = strlen(line);
if (len > 0 && line[len - 1] == '\n')
line[len - 1] = '\0';Input is never automatically valid
Nothing about scanf, fgets, or any other input function guarantees the data coming in is well-formed, in range, or even the right type at all — a program has to check for itself, every time, before trusting what it read.
This program checks in two separate steps — first, whether scanf even succeeded at converting a number at all, and second, whether the number it got actually makes sense — a pattern (read, then validate, then either handle the error or use the data) worth repeating for any input a program can't fully trust.
#include <stdio.h>
int main(void)
{
int age;
printf("Enter your age: ");
if (scanf("%d", &age) != 1)
{
printf("That wasn't a valid number.\n");
return 1;
}
if (age < 0 || age > 130)
{
printf("That age doesn't seem realistic.\n");
return 1;
}
printf("Age accepted: %d\n", age);
return 0;
}Three levels of I/O, from coarse to fine
C's I/O functions aren't really one flat list — they sit at three distinct levels of abstraction, and picking the right one for the job matters.
Formatted I/O is the most convenient when the data genuinely has a type (an int, a float); character I/O is the finest-grained tool, useful for scanning input byte by byte; line-oriented I/O is usually the safest and most predictable choice for free-form text.
formatted I/O printf / scanf / fprintf / fscanf - typed values, via % specifiers
character I/O getchar / putchar - exactly one character at a time
line/string I/O fgets / puts - a whole line or string at onceA tour of printf conversions in one program
Seeing several conversions used together, on realistic data, reinforces how each one is meant to be used.
%d for a signed count, %u for a value that's conceptually never negative, %c for one character, %.2f to keep a decimal average readable, %s for text, and %x to view flags in the base programmers often find more natural than decimal.
#include <stdio.h>
int main(void)
{
int id = 42;
unsigned int views = 1500;
char grade = 'A';
double average = 87.6543;
char username[] = "codebreaker";
int flags = 255;
printf("ID: %d\n", id);
printf("Views: %u\n", views);
printf("Grade: %c\n", grade);
printf("Average: %.2f\n", average);
printf("User: %s\n", username);
printf("Flags (hex): %x\n", flags);
return 0;
}printf's own return value
printf isn't a void function — it returns an int giving the number of characters it successfully transmitted, or a negative number if an output error occurred. Most code never looks at this return value, but it exists and is occasionally useful.
#include <stdio.h>
int main(void)
{
int written = printf("Hello\n");
printf("That call wrote %d characters.\n", written); // 6 (Hello + \n)
return 0;
}getchar's return value and reading until EOF
Because getchar() returns int rather than char, it can distinguish every real character from EOF, the special value signaling no more input is available — which makes a simple loop possible for consuming input of any length, one character at a time, until it truly runs out.
This copies standard input to standard output one character at a time until end-of-input, which the user can typically trigger by pressing Ctrl+D (Unix-like systems) or Ctrl+Z (Windows).
ch must be declared as int, not char — storing EOF into a char can, on some platforms, corrupt or misrepresent it, breaking the loop's exit condition.
getchar() reads one character (or returns EOF) and that result is assigned into ch inside the loop's own condition, then compared against EOF.
As long as ch isn't EOF, putchar(ch) writes it right back out, and the loop reads the next character.
#include <stdio.h>
int main(void)
{
int ch;
while ((ch = getchar()) != EOF)
{
putchar(ch);
}
return 0;
}A quick-reference table of the basic I/O functions
printf(fmt, ...) write formatted output to stdout
scanf(fmt, &var, ...) read formatted input from stdin
getchar() read one character from stdin, returns int
putchar(c) write one character to stdout
fgets(buf, size, stdin) read one line (bounded) from stdin
puts(str) write a string to stdout, plus a trailing newline
fprintf(stream, fmt,...) write formatted output to any stream (e.g. stderr)
fscanf(stream, fmt,...) read formatted input from any streamA simple mental model for the whole pipeline
One picture is enough to hold every idea on this page at once.
Data flows in through stdin via one of three reading functions, gets processed by your program's own logic, and flows back out through stdout via one of three writing functions — with stderr running alongside stdout as an entirely independent channel for diagnostics.
keyboard
|
stdin -----> scanf / getchar / fgets -----> your program
|
printf / putchar / puts
|
stdout -----> terminal
(your program) -----> fprintf(stderr, ...) -----> stderr -----> terminal
(a separate path from stdout)Best practices to carry forward
A handful of habits cover most of what separates I/O code that just barely works from I/O code that holds up under real, occasionally messy, user input.
Always match a format specifier to its argument's actual type — %d for int, %f for float or double when printing (but %lf specifically when reading a double with scanf), %c for a single character, %s for a null-terminated string. A mismatch compiles cleanly and fails silently.
Check scanf's return value whenever the input isn't fully under your control — comparing it against the number of values you expected is the only dependable way to notice a bad or missing value before it corrupts everything computed from it.
Prefer fgets over scanf("%s", ...) for reading free-form text, since fgets enforces its own size limit and can never overflow the buffer it's given; if scanf's %s must be used, always pair it with an explicit width limit.
Keep output readable on purpose — explicit \n placement, consistent field widths for tabular data, and fixed decimal precision for money or measurements all cost little and prevent output that's technically correct but hard to read.
Send diagnostics and error messages to stderr with fprintf(stderr, ...) rather than mixing them into stdout with printf, so a program's real output and its warnings can always be told apart, redirected separately, or filtered independently.
printf sends formatted text to standard output (usually your terminal); scanf reads formatted input from standard input (usually your keyboard). Both rely on a format string containing % specifiers that describe the type of each value being printed or read.
A program that can't take input or show output is useless for anything interactive — I/O is how a C program actually communicates with the person or system running it.
Every command-line C tool ever written; debugging output during development; and, at a lower level, the same underlying idea (formatted, typed I/O) extends into file handling later in this track.
printf's format string is like a fill-in-the-blank form: '%d apples and %s' has two blanks, and you must supply exactly the right type of value for each blank in order, or the form comes out garbled.
Reading one integer The program prompts for input, reads an integer typed by the user, and echoes it back in a full sentence.
printf("Enter your age: ") prints the prompt with no trailing newline, so the cursor stays on the same line waiting for input.
scanf("%d", &age) pauses the program and waits for the user to type something; %d tells scanf to interpret it as an integer.
&age passes the memory address of age, not its value — scanf needs to know where to store the number it reads, so it must be given a pointer (the & 'address-of' operator, covered fully in the Pointers Fundamentals topic).
Once the user presses Enter, scanf parses the typed text as an integer and writes it into the memory at &age.
The final printf substitutes the now-filled-in age into the sentence and prints it.
25 (after scanf writes it) scanf needs &age (age's address) because it has to reach into that exact box to fill it in — passing age itself would only hand it a copy of whatever garbage was already there.
Reading two values in one call A single scanf call can read several values at once, each with its own format specifier and its own & address argument.
scanf("%d %f", &quantity, &price) reads two whitespace-separated values from one line of input (e.g. typing '3 9.99' and pressing Enter) — the space in the format string tells scanf to skip any whitespace between them.
The first value is parsed as an int and written into quantity; the second is parsed as a float and written into price.
quantity * price multiplies an int by a float — C automatically promotes quantity to float first so the multiplication makes sense, producing a float result.
int age; scanf("%d", age);
int age; scanf("%d", &age);
Without &, scanf receives age's current (garbage, uninitialized) value reinterpreted as a memory address, and tries to write the input there — undefined behavior that usually crashes the program.
double price; scanf("%f", &price);
double price; scanf("%lf", &price);
scanf, unlike printf, does not treat float and double the same way — %f tells it to write 4 bytes (a float), but price is 8 bytes (a double), corrupting adjacent memory. %lf is required for reading into a double.
Common format specifiers: %d (int), %f (float/double when printing — note scanf needs %lf for double), %c (char), %s (a null-terminated string/char array), %p (a pointer address), %x (hexadecimal).
scanf returns the number of items it successfully read — a return value of 0 or a mismatch with what you expected means the input didn't match the format string, and is worth checking in real programs.
printf does NOT automatically add a newline — \n must be included explicitly wherever you want one, unlike some other languages' default print functions.
%s with scanf stops reading at the first whitespace character, which is why scanf alone is a poor choice for reading a full line of text containing spaces (fgets is the safer tool for that, covered in the Strings topic).
Forgetting the & before a variable in scanf (scanf("%d", age) instead of &age) — this passes the variable's value as if it were a memory address, which is undefined behavior and a classic beginner crash.
Mismatching the format specifier and the actual variable type — printf("%d", someFloat) reads the wrong number of bytes off the argument list and prints garbage, since printf has no way to check your types itself.
Using %f in scanf for a double instead of %lf — printf treats float and double the same way due to default argument promotion, but scanf does not, and this mismatch is a very common silent bug.
Ignoring scanf's return value in a program where malformed input is a real possibility, letting a single bad input value cascade into garbage data further down the program.
Always double-checking that every %-specifier in a printf/scanf call matches its corresponding argument's real type, especially after refactoring a variable's type.
Using fgets instead of scanf("%s", ...) whenever you need to safely read a full line of user input, including spaces.
Checking scanf's return value against the number of items you expected it to successfully parse, in any program where you can't fully trust the input.
printf and scanf are variadic functions Both are declared to accept a variable number of arguments (via C's <stdarg.h> mechanism, covered later in this track) — this is exactly why the compiler cannot type-check your format specifiers against your arguments for you; a mismatch is a runtime problem, not a compile-time one, on most compilers (though modern GCC/Clang add a helpful -Wformat warning as a compile-time safety net).
Buffering: stdout is usually line-buffered or fully buffered
Output written with printf isn't always sent to the terminal instantly — it's often held in an internal buffer and 'flushed' when a newline appears, the buffer fills, or the program exits normally. This is why output can appear out of order relative to error messages (which typically go to the unbuffered stderr) if you don't flush explicitly with fflush(stdout) when it matters.
Why does scanf need &variable instead of just variable for most types?
scanf needs to know the memory address to write the parsed value into. Passing the variable itself would only give scanf a copy of its current value, with no way to modify the original — so it requires a pointer (the address) instead.
What's the correct scanf specifier for reading into a double, and why does it differ from printf's?
%lf. printf uses %f for both float and double because of default argument promotion (float arguments are promoted to double when passed through a variadic function like printf), but scanf receives a pointer, which is never promoted, so it must be told explicitly which size to expect.
Why is scanf("%s", buffer) considered risky?
It reads until the next whitespace with no bound on length, so input longer than buffer's allocated size overflows the buffer — the same category of bug covered in depth in the Undefined Behavior & Common Pitfalls topic.
scanf has to write the value it parses directly into the caller's memory, but C passes arguments by value, so it can't modify variable itself through an ordinary copy. Passing &variable gives it the variable's address instead, which is what lets it reach in and store the result there.
What's the practical difference between printf and puts?
puts writes a plain string and automatically appends one newline, with no support for % format specifiers at all; printf supports formatting values into the output but never adds a newline on its own — \n has to be written explicitly wherever one is wanted.
Why does getchar() return an int instead of a char?
So it has room for one value beyond every real character: EOF, a special value used to signal that there's no more input. If getchar() returned a plain char, every possible char value could be a real character, leaving no distinct value free to represent end-of-input.
What's the difference between stdout and stderr, given that both usually print to the same terminal?
They are two independent output streams. stdout is meant for a program's normal output and is what printf targets by default; stderr is meant for diagnostics and error messages and is written to with fprintf(stderr, ...). Redirecting one (for example into a log file) does not affect the other, since they aren't actually the same channel just because they look that way on screen by default.
What does it mean for stdout to be 'buffered', and why does that matter?
It means text passed to printf isn't necessarily sent to its destination the instant printf is called — it can sit in an internal buffer until a newline triggers a flush, the buffer fills, or the program exits. It matters because output can appear later than expected (or interleaved oddly with stderr, which is typically unbuffered), and because fflush(stdout) is sometimes needed to force output out immediately, such as before a long delay.
What exactly does this program print, and why doesn't it appear as three separate lines?
It prints: 7 77 (with the second and third 7 on one line together, followed by a newline). Only the first and third printf calls include \n — the second one doesn't, so its output runs directly into the third call's output before that one finally starts a new line.
What is wrong with this line, and what does it actually do at runtime?
It's missing the & before count. scanf needs the address of the variable to write into; without &, it receives count's current, uninitialized value and treats that garbage number as if it were a memory address, then attempts to write the parsed input there — undefined behavior that commonly crashes the program. The fix is scanf("%d", &count);.
This compiles, but reading a value into price does not work correctly. Why not, and what's the fix?
%f in scanf tells it to write a 4-byte float's worth of data, but price is an 8-byte double — scanf writes half the bytes the double actually needs and corrupts adjacent memory in the process. Unlike printf, scanf does not treat float and double the same way, since it receives a pointer rather than a promoted value. The fix is scanf("%lf", &price);.
Assuming the user types 5 and presses Enter, then never gets a second prompt, what value ends up in b, and why?
b ends up holding the newline character left over from pressing Enter after typing 5 ('\n', which prints as 10 with %d) — not whatever the user might have typed next. scanf("%d", &a) reads only the digits, leaving the Enter key's newline sitting unread in the input buffer, and the very next scanf("%c", &b) immediately consumes that leftover newline instead of waiting for new input. The fix is scanf(" %c", &b); with a leading space.
Write a program that reads two integers with scanf, checks that both were read successfully, and prints their sum — or an error message if the input was invalid.
Checking that scanf's return value equals 2 (not just truthy) confirms both integers were actually parsed before the sum is computed and printed.
Write a program that safely reads a line of text (up to 99 characters) into a buffer using fgets, and removes the trailing newline if present.
fgets bounds the read to sizeof(buffer), so it can never overflow the array; checking for the newline before removing it avoids cutting off real data on a line that was too long to fit.
What does this print, including the number on the second line?
It prints: abc 3 printf returns the number of characters it successfully wrote — "abc" is 3 characters, so total is 3, and the second printf call shows that value.
Write a program that copies standard input to standard output one character at a time, converting every lowercase letter to uppercase as it goes, stopping at end-of-input.
getchar() must be stored in an int (not a char) so its result can be distinguished from every real character when compared against EOF; toupper converts each character before putchar writes it back out.
If the user types "hello world" at this scanf call, what actually happens, and why is it dangerous even though buf is 6 bytes?
scanf("%s", buf) reads only up to the first whitespace, so it would try to store "hello" plus a terminating '\0' — 6 bytes — into a 6-byte array, which just barely fits in this exact case, but there's no bound enforced at all: any word 6 characters or longer overflows buf and corrupts whatever memory follows it, since %s has no built-in awareness of buf's size. A safer version supplies an explicit width, scanf("%5s", buf), or better yet uses fgets(buf, sizeof(buf), stdin) instead.
What does each line print, and what's the difference between them?
The first prints " 7|" (7 padded on the left with spaces to a width of 5); the second prints "7 |" (7 padded on the right instead, left-aligned within the same width of 5). A plain width like %5d right-aligns by default; adding a - flag, as in %-5d, left-aligns within that same field width.
#include <stdio.h>
int main() {
int age;
printf("Enter your age: ");
scanf("%d", &age);
printf("You are %d years old.\n", age);
return 0;
}#include <stdio.h>
int main() {
int quantity;
float price;
printf("Enter quantity and price: ");
scanf("%d %f", &quantity, &price);
printf("Total: %.2f\n", quantity * price);
return 0;
}int x = 7;
printf("%d\n", x);
printf("%d", x);
printf("%d\n", x);int count;
scanf("%d", count); // note: no &double price;
scanf("%f", &price);int a;
char b;
scanf("%d", &a);
scanf("%c", &b);
printf("a=%d b=%d\n", a, b);#include <stdio.h>
#include <string.h>
int main(void)
{
char buffer[100];
// read a line here, then strip its newline
return 0;
}int total = printf("abc");
printf("\n%d\n", total);#include <stdio.h>
#include <ctype.h>
int main(void)
{
int ch;
// read and transform characters here, until EOF
return 0;
}char buf[6];
scanf("%s", buf);