DEV Community

Yunus Emre Güzel
Yunus Emre Güzel

Posted on Originally published at blog.emreguzel.ca

scanf vs fgets in C: Which One to Use (and Why scanf Skips Input)

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);
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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 */
Enter fullscreen mode Exit fullscreen mode
/* 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" */
Enter fullscreen mode Exit fullscreen mode

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 */
Enter fullscreen mode Exit fullscreen mode

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 */
Enter fullscreen mode Exit fullscreen mode

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 */
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

Why this is better than scanf("%d", &age) directly:

  • Bad input is consumed. If the user types “abc”, fgets takes the whole line, so there is nothing left to poison the next read. With scanf, “abc” stays in the buffer and every later read fails too.

  • You can report the error. sscanf returns how many fields it matched, so != 1 tells 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
Enter fullscreen mode Exit fullscreen mode

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 */
}
Enter fullscreen mode Exit fullscreen mode

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)