When reading from a source, often a program wants to read data a few bytes at a time. For instance, the program can ask the OS for the next 4 bytes from a file, so to read the next 32-bit integer. Then, it can repeat the operation again and again, until all the integers have been read.
However, that is rather inefficient. First, disk (or even SSD) reads are done in hardware using large chunks, only, like 512 or more bytes. Reading that much only for 4 bytes is a waste of band. Second, switching between user mode and kernel mode has its cost, and it would be better if any such switch would transfer a non-trivial amount of data.
For that reason, reading is usually done in large chunks, using a "buffer". Basically, the OS reads a large-ish portion of the file (say, a few KBs), and keeps this in a kernel-space array (the "buffer"). This is done in one large chunk so to optimize disk/SSD access. After this, when the program asks for the next bytes, the OS does not need to involve the hardware, and can return data from the buffer.
Context switches are similarly optimized. The C library also commonly reads a large chunk form the OS, using a user-space buffer, using only one system call (= one switch). In this way, when the application program asks for the next (say) 4 bytes, the C library can copy from the buffer without making system calls.
When the buffer becomes empty, the C library starts asking again to the OS, and the OS starts asking again to the hardware.
Buffered writing is similar, but in the other direction, converting many small writes to fewer big ones, so to improve performance. This is done by the C library and the OS/kernel.