[I'm using Perl in the examples for conciseness and ease of reproduction, but the concepts I'm illustrating are not specific to Perl. C works in the same manner.]
Buffering dictates how often stuff written to a file handle is flushed (sent) to the OS. Compare the behaviour of the following two commands:
# With buffering (default)
perl -e'$|=0; print "a"; sleep(2); print "b\n";'
# Without buffering
perl -e'$|=1; print "a"; sleep(2); print "b\n";'
Normally, the buffer is only flushed when it becomes full. Line-buffered output is also flushed when a newline is encountered. Compare:
perl -e'print "a"; sleep(2); print "b\n";'
perl -e'print "a\n"; sleep(2); print "b\n";'
Most programs use block-buffering. However, they usually switch to line-buffering for stdout when it's connected to a terminal. Compare:
# Perl's STDOUT is line-buffered when connected to a terminal.
perl -e'print "a\n"; sleep(2); print "b\n";'
# Perl's STDOUT is fully buffered when connected to a pipe.
perl -e'print "a\n"; sleep(2); print "b\n";' | cat
# unbuffer uses pseudo-ttys to fool a program into thinking it's connected to a terminal.
unbuffer perl -e'print "a\n"; sleep(2); print "b\n";' | cat