The behavior you observe has nothing to do with C and getchar() , but with the teletype subsystem (TTY) in the kernel.
To do this, you need to know how the processes get their input from your keyboard and how they write their output to your terminal window (I assume that you are using UNIX, and the following explanations are specific to UNIX, i.e. Linux, macOS, etc. d.):

The Terminal window in the diagram above is your terminal window, for example, xterm, iTerm or Terminal.app. In the old days, these were terminals where individual hardware devices consisted of a keyboard and a screen, and they were connected to a (possibly remote) computer via a serial line (RS-232). Each character typed on the terminal keyboard was sent on this line to the computer and used by the application that was connected to the terminal. And each character created by the application as output was sent along the same line to the terminal that displayed it on the screen.
Currently, terminals are no longer hardware devices, but they move “inside” the computer and become processes called terminal emulators . xterm, iTerm2, Terminal.app, etc. - all these are terminal emulators.
However, the communication mechanism between applications and terminal emulators has remained the same as for hardware terminals. Terminal emulators emulate hardware terminals. This means that from the point of view of the application, communication with a terminal emulator today (for example, iTerm2 ) works in the same way as communication with a real terminal (for example, DEC VT100 ) in 1979. This mechanism was left unchanged, so applications developed for hardware terminals will still work with software terminals of emulators.
So how does this communication mechanism work? The UNIX kernel has a subsystem called TTY (TTY means teletype, which was the earliest form of computer terminals that didn't even have a screen, just a keyboard and printer). You can think of TTY as a generic terminal driver . TTY reads bytes from the port to which the terminal is connected (received from the keyboard of the terminal), and writes bytes to this port (sent to the terminal display).
There is a TTY instance for each terminal connected to the computer (or for each terminal emulator process running on the computer). Therefore, a TTY instance is also called a TTY device (from the point of view of the application, talking to a TTY instance is like talking to a terminal device). In the UNIX way of making driver interfaces accessible as files, these TTY devices appear as /dev/tty* in some form, for example, on macOS they are called /dev/ttys001 , /dev/ttys002 , etc.
An application can have its own standard streams (stdin, stdout, stderr) directed to a TTY device (in fact, this is the default value, and you can find out which TTY device your shell is connected to with the tty command). This means that everything that the user types on the keyboard becomes the standard input of the application, and everything that the application writes to its standard output is sent to the terminal screen (or to the terminal window of the terminal emulator). All this happens through the TTY device, that is, the application only communicates with the TTY device (this type of driver) in the kernel.
Now the decisive moment: the TTY device does more than just pass each input character to the standard input of the application. By default, a TTY device applies what is called line discipline to received characters. This means that it buffers them locally and interprets the delete, one-line return, and other line-editing characters and passes them to standard application input only when it receives a carriage return or line feed, which means the user has finished entering and editing whole line.
This means that until the user presses return, getchar() will not see anything in stdin. As if nothing had been printed so far. Only when the user presses the return key does the TTY device send these characters to the standard input of the application, where getchar() immediately reads them as.
In this sense, the behavior of getchar() is nothing special. It just immediately reads the characters in stdin as they appear. The string buffering you observe occurs on the TTY device in the kernel.
Now for the interesting part: this TTY device can be customized. You can do this, for example, from the shell using the stty command. This allows you to customize almost every aspect of line discipline that the TTY applies to incoming characters. Or you can turn off any processing by setting the TTY device to raw mode . In this case, the TTY device immediately redirects each received character to the standard application screen without any form of editing.
If you enable raw mode in your TTY device, you'll see that getchar() immediately gets every character that you type on the keyboard. The following C program demonstrates this:
#include <stdio.h> #include <unistd.h> // STDIN_FILENO, isatty(), ttyname() #include <stdlib.h> // exit() #include <termios.h> int main() { struct termios tty_opts_backup, tty_opts_raw; if (!isatty(STDIN_FILENO)) { printf("Error: stdin is not a TTY\n"); exit(1); } printf("stdin is %s\n", ttyname(STDIN_FILENO)); // Back up current TTY settings tcgetattr(STDIN_FILENO, &tty_opts_backup); // Change TTY settings to raw mode cfmakeraw(&tty_opts_raw); tcsetattr(STDIN_FILENO, TCSANOW, &tty_opts_raw); // Read and print characters from stdin int c, i = 1; for (c = getchar(); c != 3; c = getchar()) { printf("%d. 0x%02x (0%02o)\r\n", i++, c, c); } printf("You typed 0x03 (003). Exiting.\r\n"); // Restore previous TTY settings tcsetattr(STDIN_FILENO, TCSANOW, &tty_opts_backup); }
The program puts the TTY device of the current process into raw mode, then uses getchar() to read and print characters from stdin in a loop. Characters are printed as ASCII codes in hexadecimal and octal. The program specifically interprets the ETX character (ASCII code 0x03) as a trigger to terminate. You can create this character on the keyboard by typing Ctrl-C .