'\n' vs std::endl
A Codeforces problem. A friend's tip: swap std::endl for '\n'. Output got faster. The reason wasn't obvious — so here's the breakdown.
The core difference
It comes down to one thing: std::endl flushes the buffer. '\n' does not.
Under the hood, std::endl is doing two things in one call:
cout << '\n';
cout.flush(); // <-- this is the expensive partFlushing forces the OS to drain the output buffer immediately via a write()
syscall — a round-trip from user space to kernel space. Do that once and it
costs almost nothing. Do it inside a loop that runs a million times, and it
starts to matter.
But that raises an obvious question: does it actually matter in practice? Time to measure.
Benchmark — v1
#include <bits/stdc++.h>
#include <chrono>
using namespace std;
void nothing(const int i) { cout << i; }
void newline(const int i) { cout << i << '\n'; }
void endline(const int i) { cout << i << endl; }
auto time(void (*out)(int), const int iterations) {
const auto start{chrono::steady_clock::now()};
for (int i = 0; i < iterations; ++i) out(i);
const auto end{chrono::steady_clock::now()};
return chrono::duration<double>{end - start};
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
constexpr int iterations = 1000;
cerr << "None: " << time(nothing, iterations).count() << "s\n";
cerr << "Newline: " << time(newline, iterations).count() << "s\n";
cerr << "Endline: " << time(endline, iterations).count() << "s\n";
}Results
| Iterations | nothing | '\n' | endl |
|---|---|---|---|
| 1M | 0.195s | 0.832s | 0.813s |
| 10M | 1.584s | 7.745s | 7.865s |
| 100M | 16.471s | 74.049s | 74.369s |
Two things jump out. First, '\n' is ~4.5× slower than nothing — that's
expected, writing an extra character has a cost. But second, '\n' and endl
are almost identical. If endl flushes and '\n' doesn't, why is there no
gap?
The answer is that the test is measuring the wrong thing. The benchmark runs against a terminal, and terminals have their own opinion about flushing.
How output buffering actually works
The C++ standard library doesn't write to the OS on every character — it accumulates data in an internal buffer first. When and how it flushes that buffer depends on the mode:
Block buffering kicks in when output goes to a file or pipe. Characters pile
up in a ~4–8 KB buffer. The OS only sees a write() when the buffer fills, or
when an explicit flush is requested. One syscall per buffer-full — very
efficient.
[char][char][char]...[char] → buffer full → write() → OS
Line buffering kicks in when output goes to an interactive terminal. The
buffer flushes automatically on every '\n'. This keeps interactive output
responsive — ls and echo need to appear line by line, not in chunks.
[char][char]\n → write() → terminal
[char][char]\n → write() → terminal
This is why the v1 results looked strange. Running against a terminal, '\n'
was already triggering a flush on its own — making it behaviorally identical to
endl. To see the real difference, the output needs to go somewhere that uses
block buffering. Redirecting to a file does exactly that.
Benchmark — v2 (block buffered)
Rerunning with output redirected to a file (./bench > /dev/null):
| Iterations | Line buffered (terminal) | Block buffered (file) | ||||
|---|---|---|---|---|---|---|
nothing
|
'\n'
|
endl
|
nothing
|
'\n'
|
endl
|
|
| 1M | 0.195s | 0.832s | 0.813s | 0.106s | 0.114s | 0.542s |
| 10M | 1.584s | 7.745s | 7.865s | 0.796s | 1.158s | 5.530s |
| 100M | 16.471s | 74.049s | 74.369s | 7.478s | 11.417s | 54.439s |
The block-buffered column is where the difference shows up.
Reading the numbers
Two independent effects are visible in the data.
Effect 1 — The flush penalty ('\n' vs endl, block buffered)
In block-buffered mode, endl is consistently ~4.8× slower than '\n':
1M: 0.114s vs 0.542s → 4.8×
10M: 1.158s vs 5.530s → 4.8×
100M: 11.42s vs 54.44s → 4.8×
Every endl forces a syscall. Every '\n' lets the buffer absorb the character
and batch the write. At 100M iterations, that difference compounds to over 43
wasted seconds — time spent bouncing between user space and kernel space for no
reason.
Effect 2 — The line-buffer penalty (nothing vs '\n', terminal)
In line-buffered mode, '\n' is already ~4.5× slower than nothing:
1M: 0.195s vs 0.832s → 4.3×
10M: 1.584s vs 7.745s → 4.9×
100M: 16.47s vs 74.05s → 4.5×
Because the terminal flushes on every newline, '\n' in line-buffered mode
behaves almost identically to endl. The gap between them is just ~2% — the
overhead of the extra flush() call on top of the flush '\n' already
triggers.
This explains why Codeforces judges produce a clear difference while a local terminal test does not: online judges redirect output to a file, putting the stream in block-buffered mode where the flush penalty is fully exposed.
When std::endl is actually the right tool
Flushing is not always wrong. There are situations where it is exactly the right call:
- Crash-adjacent logging — flush before a risky operation to ensure the message survives if the program aborts.
- Interactive prompts —
cout << "Enter name: " << endlmust appear before the program blocks oncin. - Pipe consumers — if a downstream process is waiting for data, an unflushed buffer means it never arrives until the buffer fills naturally.
In all other cases — especially tight output loops — prefer '\n' and call
cout.flush() explicitly at the one place it is actually needed.
'\n' moves the cursor. std::endl moves the cursor and pays a syscall tax
— on a judge, in a file, in a pipe, that tax always compounds.
So what did a write call that allow to change line ????