Question

Why `while (!feof(fp))` Is Wrong in C File Reading

cfilewhile-loopeoffeof

Question

In C, why is it incorrect to use feof() as the condition that controls a file-reading loop?

For example:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv)
{
    char *path = "stdin";
    FILE *fp = argc > 1 ? fopen(path = argv[1], "r") : stdin;

    if (fp == NULL) {
        perror(path);
        return EXIT_FAILURE;
    }

    while (!feof(fp)) {  /* THIS IS WRONG */
        /* Read and process data from file... */
    }

    if (fp != stdin && fclose(fp) != 0) {
        perror(path);
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

What is wrong with this loop, and what is the correct pattern for reading from a file until no more input is available?

Short Answer

By the end of this page, you will understand why feof() does not predict the future in C. You will learn that the end-of-file flag is only set after a read attempt fails, why while (!feof(fp)) often causes one extra loop iteration or bad data handling, and how to correctly write file-reading loops using fgets(), fscanf(), fread(), or fgetc() based on the operation you are performing.

Concept

feof(fp) tells you whether the end-of-file indicator is currently set for a stream. That sounds useful, but the key detail is this:

  • The EOF indicator is not set until you try to read past the end.
  • That means feof(fp) is a result check, not a loop control predictor.

So this pattern is flawed:

while (!feof(fp)) {
    /* try to read */
}

Why? Because before the final read attempt, feof(fp) is still false. The loop enters one more time, your code attempts a read, and that read fails. Only then does EOF become true.

This usually leads to problems such as:

  • processing stale data from the previous iteration
  • running the loop one extra time
  • confusing end-of-file with read errors
  • writing fragile code that behaves differently depending on the input function used

The real rule

In C, you should control the loop using the return value of the read operation itself.

Examples:

  • fgets() returns NULL on failure or EOF
  • fscanf() returns the number of successfully read items
  • fread() returns the number of elements actually read
  • fgetc() returns EOF when it cannot read a character

That means the correct question is not:

  • "Have I reached EOF yet?"

Instead, ask:

  • "Did this read succeed?"

Why this matters in real programs

Real file-reading code must handle at least two cases correctly:

  • normal end of input
  • read failure

feof() alone cannot do that before the read happens. If you use the read result directly, your code naturally handles both cases more safely and clearly.

Mental Model

Think of reading a file like taking pages from a stack.

  • feof(fp) is not checking whether there are pages left in advance.
  • It only tells you that you already tried to take a page and there was none.

So while (!feof(fp)) is like saying:

  • "As long as nobody has told me the stack is empty, keep working."

But the only way to find out the stack is empty is to try taking one more page.

That means the loop discovers the file is finished too late.

A better model is:

  • "Try to take a page. If you got one, process it. If not, stop."

That is exactly what this pattern does:

while (fgets(buffer, sizeof buffer, fp) != NULL) {
    /* process buffer */
}
Take Quiz

Syntax and Examples

Correct pattern: use the read operation in the condition

Reading lines with fgets()

#include <stdio.h>

int main(void) {
    FILE *fp = fopen("data.txt", "r");
    char buffer[256];

    if (fp == NULL) {
        perror("data.txt");
        return 1;
    }

    while (fgets(buffer, sizeof buffer, fp) != NULL) {
        printf("Read: %s", buffer);
    }

    fclose(fp);
    return 0;
}

Here, the loop continues only when fgets() successfully reads a line.

Reading formatted input with fscanf()

#include <stdio.h>

int main(void) {
    FILE *fp = fopen("numbers.txt", "r");
    int x;

    if (fp == NULL) {
        perror("numbers.txt");
        return 1;
    }

    while (fscanf(fp, "%d", &x) == 1) {
        printf("Number: %d\n", x);
    }

    fclose(fp);
    return 0;
}

fscanf() returns how many items it successfully read. For one integer, success means the return value must be 1.

Reading bytes with fread()

#include <stdio.h>

int main(void) {
    FILE *fp = fopen("data.bin", "rb");
    unsigned char buf[64];
    size_t n;

    if (fp == NULL) {
        perror("data.bin");
        return 1;
    }

    while ((n = fread(buf, 1, sizeof buf, fp)) > 0) {
        fwrite(buf, 1, n, stdout);
    }

    fclose(fp);
    return 0;
}

This processes only the bytes that were actually read.

A broken pattern

while (!feof(fp)) {
    fgets(buffer, sizeof buffer, fp);
    printf("%s", buffer);
}

Problems:

  • the loop may execute once more after the last successful read
  • fgets() may return NULL
  • buffer may still contain old data from the previous iteration
  • the program may print the last line twice or behave unpredictably

Step by Step Execution

Consider this file content:

apple
banana

Now look at this incorrect code:

char buffer[100];
while (!feof(fp)) {
    fgets(buffer, sizeof buffer, fp);
    printf("%s", buffer);
}

What happens step by step

Before loop starts

  • EOF flag is not set
  • !feof(fp) is true
  • loop enters

First iteration

  • fgets() reads apple\n
  • buffer contains apple\n
  • printf() prints apple
  • EOF is still not set

Second iteration

  • !feof(fp) is still true
  • loop enters again
  • fgets() reads banana\n
  • buffer contains banana\n
  • printf() prints banana
  • EOF is still not set yet

Third iteration

  • !feof(fp) is still true
  • loop enters again
  • fgets() tries to read, but there is no more data
  • fgets() returns NULL
  • EOF indicator is now set
  • but your code still does printf("%s", buffer);
  • buffer still contains the old value banana\n
  • banana may be printed again

That extra iteration is the core bug.

Correct version

char buffer[100];
while (fgets(buffer, sizeof buffer, fp) != NULL) {
    printf("%s", buffer);
}

Now the loop only runs when reading succeeded:

  1. read apple
  2. process apple
  3. read banana
  4. process banana
  5. next fgets() fails
  6. stop immediately

No extra iteration happens.

Real World Use Cases

This concept appears everywhere input is processed.

Reading log files

A program may read one line at a time from a server log:

while (fgets(line, sizeof line, logFile) != NULL) {
    /* parse log entry */
}

Importing CSV or text data

When reading rows from a data file, you should stop when the read fails, not when feof() happens to change later.

Parsing configuration files

Applications often read key-value lines until input ends:

  • database config files
  • environment-style text files
  • simple app settings

Copying binary files

For images, archives, or audio files, fread() is the correct basis for loop control:

while ((n = fread(buf, 1, sizeof buf, src)) > 0) {
    fwrite(buf, 1, n, dst);
}

Reading from standard input

Interactive tools and shell pipelines often use:

while (fgets(line, sizeof line, stdin) != NULL) {
    /* handle user input or piped data */
}

This works whether input comes from a keyboard, a redirected file, or another command.

Take Quiz

Real Codebase Usage

In real C codebases, developers usually build input loops around successful reads, then check feof() or ferror() afterward if they need to know why input stopped.

Common pattern: read first, then inspect state

while (fgets(buf, sizeof buf, fp) != NULL) {
    /* process line */
}

if (ferror(fp)) {
    perror("read failed");
}

This is clearer because:

  • the loop condition matches the real action
  • processing only happens for valid data
  • error handling is separated from normal input flow

Guard clause style

Programs often validate resources early:

FILE *fp = fopen(path, "r");
if (fp == NULL) {
    perror(path);
    return EXIT_FAILURE;
}

Then they use a tight read loop based on the input function's return value.

Handling partial reads

With binary I/O, partial reads are normal near EOF. Real code handles the returned size carefully:

while ((n = fread(buf, 1, sizeof buf, fp)) > 0) {
    process_bytes(buf, n);
}

if (ferror(fp)) {
    /* handle disk or stream error */
}

Distinguishing EOF from errors

After a loop ends, code may check:

  • feof(fp) to confirm normal end-of-file
  • ferror(fp) to detect a read error

That is an appropriate use of feof().

Useful rule

  • Use feof() to inspect what happened after reading stops.
  • Do not use feof() to decide whether the next read will succeed.

Common Mistakes

1. Using feof() before reading

Broken code:

while (!feof(fp)) {
    fscanf(fp, "%d", &x);
    printf("%d\n", x);
}

Why it is wrong:

  • feof() is still false before the failed read
  • the final fscanf() may fail
  • x may keep its old value and be printed again

Fix:

while (fscanf(fp, "%d", &x) == 1) {
    printf("%d\n", x);
}

2. Ignoring the return value of the input function

Broken code:

fgets(buf, sizeof buf, fp);
process(buf);

If fgets() fails, buf should not be processed.

Fix:

if (fgets(buf, sizeof buf, fp) != NULL) {
    process(buf);
}

3. Confusing EOF with error

End-of-file is not the same as a read error.

Fix:

while (fgets(buf, sizeof buf, fp) != NULL) {
    /* process */
}

if (ferror(fp)) {
    perror("read error");
}

4. Processing stale data after a failed read

Broken code:

char buf[100];
while (!feof(fp)) {
    fgets(buf, sizeof buf, fp);
    puts(buf);
}

If the last fgets() fails, buf may still contain previous content.

5. Closing stdin

In code like the original example, fp may be stdin. Many programs should not call fclose(stdin) unless they intentionally want to close standard input.

Safer pattern:

if (fp != stdin) {
    fclose(fp);
}

6. Assuming one input function behaves like another

Different functions report success differently:

  • fgets() returns a pointer or NULL
  • fscanf() returns item count
  • fread() returns number of elements read
  • fgetc() returns a character or EOF

Always check the correct return convention for the function you use.

Comparisons

PatternGood forCorrect loop conditionNotes
feof(fp)Checking stream state after reading stopsNot recommended as loop controlEOF is set only after a failed read
fgets()Reading text line by linewhile (fgets(buf, sizeof buf, fp) != NULL)Best for line-oriented text input
fscanf()Structured formatted inputwhile (fscanf(fp, "%d", &x) == 1)Must check exact item count
fgetc()Reading one character at a timewhile ((ch = fgetc(fp)) != EOF)Use int, not char, for ch
fread()Binary/block inputwhile ((n = fread(buf, 1, sizeof buf, fp)) > 0)Supports partial reads

feof() vs ferror()

FunctionWhat it tells youWhen to use it
feof(fp)End-of-file indicator is setAfter reading stops, to see if EOF was reached
ferror(fp)Error indicator is setAfter reading stops or after a failed operation

Wrong mindset vs right mindset

Wrong ideaBetter idea
"Loop until EOF becomes true.""Loop while each read succeeds."
"Check the stream first.""Attempt the read, then check the result."
"EOF means no more data is coming before I read.""EOF is discovered by a failed read attempt."
Take Quiz

Cheat Sheet

Quick rules

  • Do not use while (!feof(fp)) for reading loops.
  • EOF is set after a read attempts to go past the end.
  • Use the read function's return value to control the loop.
  • Use feof() and ferror() after the loop if needed.

Correct patterns

fgets()

while (fgets(buf, sizeof buf, fp) != NULL) {
    /* use buf */
}

fscanf()

while (fscanf(fp, "%d", &x) == 1) {
    /* use x */
}

fgetc()

int ch;
while ((ch = fgetc(fp)) != EOF) {
    /* use ch */
}

fread()

size_t n;
while ((n = fread(buf, 1, sizeof buf, fp)) > 0) {
    /* use first n bytes */
}

After the loop

if (ferror(fp)) {
    perror("read error");
}

Optional EOF check:

if (feof(fp)) {
    /* normal end of file */
}

Edge cases

  • fscanf() can stop because of invalid format, not just EOF.
  • fread() may return less than requested near EOF.
  • fgetc() result must be stored in int, not char.
  • Failed reads do not give valid new data to process.

FAQ

Why does while (!feof(fp)) read one extra time?

Because the EOF flag is only set after a read attempt fails at the end of the file. The loop condition is checked too early.

Is feof() ever useful in C?

Yes. It is useful after a read loop ends, when you want to know whether input stopped because of EOF or because of an error.

What should I use instead of feof() in a loop?

Use the return value of the reading function, such as fgets() != NULL, fscanf(...) == expected_count, or fread(...) > 0.

Can fscanf() stop for reasons other than EOF?

Yes. It can fail because the input does not match the format string. That is why checking its exact return value matters.

Should I check both feof() and ferror()?

Usually only after the loop. If a read loop ends unexpectedly, ferror() helps detect errors, and feof() can confirm normal end-of-file.

Why is processing stale data dangerous?

If a read fails and you still use the buffer or variable, you may reuse old values from the previous successful read, causing duplicate output or incorrect logic.

Is while ((ch = fgetc(fp)) != EOF) safe?

Yes, if ch is an int. fgetc() returns all possible byte values plus the special EOF value, which may not fit safely in a char.

Take Quiz

Mini Project

Description

Build a small C program that reads a text file line by line and prints each line with a line number. This project demonstrates the correct way to loop over file input without using feof() as the loop condition.

Goal

Create a program that opens a file, reads each line safely with fgets(), numbers the lines, and handles file errors correctly.

Requirements

  • Accept a filename from the command line.
  • Open the file for reading and report an error if it cannot be opened.
  • Read the file line by line using a correct loop condition.
  • Print each line prefixed with its line number.
  • After reading, report a read error if one occurred.
  • Close the file before exiting.
Take Quiz

Keep learning

Related questions

View more