The Short Answer
Use fgets to read a line of text. Use scanf to read a single formatted value when you already know exactly what it should look like.
That is the whole rule. The rest of this post is why it holds, what breaks when you get it wrong, and how to use each one safely.
What scanf Actually Does
scanf is a formatted reader. You give it a pattern, and it tries to match the input against that pattern:
int age;
scanf("%d", &age);
It skips leading whitespace, reads digits until something else appears, and stops. It leaves everything it did not consume in the input buffer — including the newline you pressed Enter to send.
That leftover is the source of almost every scanf bug you will meet. If your program is skipping input or looping forever, that is why:
What fgets Actually Does
fgets reads a whole line, up to a size limit, and keeps the newline:
char name[50];
fgets(name, sizeof name, stdin);
It reads at most 49 characters plus a null terminator. If the user typed fewer, the newline is included. If they typed more, the rest stays in the buffer for your next read.
No pattern matching, no surprise. You get a string, and you decide what to do with it.
Side by Side: Reading a Name
The same task, both ways. Reading a name with a space in it — “Emre Guzel”:
/* scanf: stops at the space */
char first[20];
scanf("%19s", first);
printf("%s\n", first); /* prints "Emre" only */
/* fgets: reads the whole line */
char full[50];
fgets(full, sizeof full, stdin);
full[strcspn(full, "\n")] = '\0'; /* strip the newline */
printf("%s\n", full); /* prints "Emre Guzel" */
Two things to notice. First, scanf("%s") cannot read a name with a space — it stops at the first whitespace. Second, fgets keeps the newline, so you strip it with strcspn.
That strcspn line is the standard idiom. It finds the position of the newline and replaces it with a null terminator. If there is no newline (the line was longer than the buffer), it does nothing harmful.
The Overflow Problem
This is the reason fgets wins for text:
char name[10];
scanf("%s", name); /* user types 50 characters -> buffer overflow */
scanf("%s") has no idea how big your buffer is. It writes as many characters as the user types, straight past the end of the array. That is a buffer overflow — undefined behaviour, and in the worst case an exploitable one.
You can limit it with a field width:
scanf("%9s", name); /* at most 9 chars + null = 10 bytes */
But the width must be one less than the buffer size, and you have to remember to change it every time you change the array. Forget once and you have the bug back.
fgets takes the size as an argument, so it cannot get out of sync:
fgets(name, sizeof name, stdin); /* always correct */
sizeof name updates automatically when you resize the array. That is the whole argument.
Where scanf Is Still the Right Tool
This is not a “never use scanf” post. scanf is better when the input is genuinely structured and you want the parsing done for you:
int day, month, year;
if (scanf("%d/%d/%d", &day, &month, &year) == 3) {
printf("Parsed: %04d-%02d-%02d\n", year, month, day);
}
Doing that with fgets means reading the line, then calling sscanf on it — which is actually the better pattern, and the next section explains why.
The Pattern That Avoids Both Problems
Read the line with fgets, then parse it with sscanf. You get fgets‘s safety and scanf‘s convenience:
char line[100];
int age;
printf("Age: ");
if (fgets(line, sizeof line, stdin) == NULL) {
return 1; /* EOF or read error */
}
if (sscanf(line, "%d", &age) != 1) {
printf("That is not a number.\n");
return 1;
}
printf("You are %d\n", age);
Why this is better than scanf("%d", &age) directly:
Bad input is consumed. If the user types “abc”,
fgetstakes the whole line, so there is nothing left to poison the next read. Withscanf, “abc” stays in the buffer and every later read fails too.You can report the error.
sscanfreturns how many fields it matched, so!= 1tells you the parse failed.Nothing overflows. The line is bounded by
sizeof line.
This is the pattern I would use in almost every interactive C program.
The Comparison Table
scanf("%s") fgets()
Reads one word one line
Stops at space yes no
Keeps newline no yes
Buffer safe no (needs width) yes (size arg)
Bad input left yes no
Returns fields matched pointer or NULL
Best for structured input text input
Checking the Return Value
Both functions tell you when they fail. Almost nobody checks:
if (scanf("%d", &age) != 1) {
/* input was not a number */
}
if (fgets(line, sizeof line, stdin) == NULL) {
/* EOF or error */
}
scanf returns the number of fields it successfully matched. fgets returns NULL on failure. Ignoring these is how programs crash on empty input.
The Rule, Stated Once
Reading text? fgets. Reading a number? fgets then sscanf. Reading a rigidly formatted string like a date? scanf is fine, but fgets + sscanf is still safer.
The reason is simple: scanf leaves bad input in the buffer and cannot know your buffer size. fgets does neither. The one extra line to strip a newline is a small price for not chasing an input bug for an hour.
If you are new to reading input in C, start with the complete guide to user input in C, then come back here. And if your program is crashing rather than misbehaving, that is usually the missing &: why your C program crashes when you forget & in scanf.
Originally published on my blog. I post new C, Python and AI-agent guides there regularly.
Top comments (0)