Sitemap
Anuj Gupta

Anuj Gupta

Senior Technical Lead, 11+ years in safety-critical C++ — aviation, medical devices, industrial automation. Writes about build tools and shipping software.

Why fork() Can Print Your Message Twice: A Stdio Buffering Trap

4 min readAug 11, 2026

--

Here’s a small experiment. Compile and run this:

#include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
int main()
{
printf("Hello World!!");
pid_t pid = fork();
if (pid == 0) {
printf("Child Process\n");
} else {
sleep(1);
wait(NULL);
printf("Parent Process\n");
}
return 0;
}

At a glance, this looks like it should print "Hello World!!" exactly once, followed by either "Child Process" or "Parent Process" depending on which branch runs. That's what most people expect. Here's what actually happens:

Hello World!!Child Process
Hello World!!child exit status 3
Parent Process

"Hello World!!" appears twice — once from a line of code that only ran once. No loop, no double call, nothing obviously wrong with the logic. So what's going on?

The Missing Newline Is the Whole Story

The bug — or rather, the surprising-but-correct behavior — comes down to one detail: printf("Hello World!!") has no trailing \n.

stdout in C is typically line-buffered when connected to a terminal. That means output isn't sent to the terminal the moment printf() is called — it's accumulated in an internal buffer and only flushed (actually written out) when a newline character appears, when the buffer fills up, or when the program exits normally.

Without a \n, "Hello World!!" doesn't get flushed. It just sits in stdio's internal buffer, unflushed, at the moment fork() is called.

What fork() Actually Duplicates

fork() creates a new process by duplicating the entire memory image of the calling process — its stack, its heap, its open file descriptors, and critically, its stdio buffers. The unflushed "Hello World!!" sitting in the buffer isn't just in the parent process anymore after fork() returns — it's now sitting in two separate copies of that buffer, one in the parent, one in the child, each unaware of the other.

Get Anuj Gupta’s stories in your inbox

Join Medium for free to get updates from this writer.

Later, when each process eventually flushes its own copy of the buffer — whether via a newline, a fflush(), or simply exiting — each one independently writes out its own copy of "Hello World!!". The parent flushes its copy once. The child flushes its copy once. Two processes, two flushes, two prints of the same string, from a single printf() call that only executed once.

Proving It

You can confirm this is exactly what’s happening by forcing a flush before the fork:

printf("Hello World!!");
fflush(stdout); // flush now — buffer is empty before fork() duplicates it
pid_t pid = fork();

or, more simply, add the newline that was missing all along:

printf("Hello World!!\n");   // newline triggers a flush in line-buffered mode
pid_t pid = fork();

Either fix produces exactly one print of "Hello World!!", because there's nothing left sitting in the buffer at the moment fork() duplicates the process.

Why This Matters Beyond a Curiosity

This isn’t just a fun gotcha — it’s a real source of confusing, hard-to-reproduce bugs in production code that forks:

  • Logging before a fork (common in daemons, servers that fork worker processes, or any pre-fork server model) can silently duplicate log lines if messages are written without flushing, especially when output is redirected to a file or pipe — which switches stdio to fully buffered mode instead of line-buffered, making the problem worse and more likely, since output accumulates for much longer before an automatic flush.
  • Output written just before fork() in any multi-process pipeline can appear more times than expected, which is confusing to debug because the logic is completely correct — the bug lives entirely in buffering state, invisible to anyone just reading the control flow.
  • It’s a good illustration of why fork() duplicates process state, not just "the code that runs next." Everything about the calling process's memory — including things you don't think of as program state, like stdio's internal buffers — gets copied too.

The General Rule

Any time you’re about to fork(), exec(), or otherwise duplicate or hand off a process, it's worth asking: is there unflushed output sitting in a buffer right now? If output is redirected to anything other than an interactive terminal — a file, a pipe, > output.log — buffering behavior changes from line-buffered to fully buffered, and the window for this kind of duplication grows significantly larger. Explicit fflush(stdout) before a fork() call, or simply disciplined use of trailing newlines, removes the ambiguity entirely.

Takeaway

The bug isn’t in fork(), and it isn't in printf() — both behave exactly as specified. The surprise comes from a detail that's easy to forget: stdio buffering is part of process state, and fork() promises to duplicate process state faithfully, buffers included. Understanding that is the difference between staring at duplicated output wondering if you called something twice, and immediately knowing to check what was sitting in the buffer when the fork happened.

--

--

Anuj Gupta
Anuj Gupta

Senior Technical Lead, 11+ years in safety-critical C++ — aviation, medical devices, industrial automation. Writes about build tools and shipping software.