INT 21h AH=02h writes one byte, the one in DL, to standard output. It is the smallest DOS output service and the usual building block for printing numbers: compute a digit, add "0", print it. Any byte can be written this way, "$" included, which INT 21h AH=09h cannot print.

prints: ABCDEFGHIJKLMNOPQRSTUVWXYZ
MOV DL, "A"
NEXT1:
MOV AH, 02h ; DOS: print the character in DL
INT 21h
INC DL ; DL survives the call
CMP DL, "Z"
JBE NEXT1
MOV DL, 13 ; CR
MOV AH, 02h ; DOS: print the character in DL
INT 21h
MOV DL, 10 ; LF
MOV AH, 02h ; DOS: print the character in DL
INT 21h

DL is kept, which is why the loop can just INC DL. AL is not: DOS returns with AL holding the character just written (a tab comes back as a space), so keep nothing precious in AL across the call, and load AH again before the next one.

Control characters

DLOn the screen
07ha beep; nothing is printed
08hthe cursor one column left; the character there stays until overwritten
09hspaces up to the next multiple of 8 columns
0Ahline feed: one line down, the same column
0Dhcarriage return: back to column 0
1Bhwith ANSI support (ANSI.SYS, or DOSBox-X's console, which has it built in): the start of an escape sequence for colours and cursor moves

A new line in DOS is the pair 0Dh 0Ah. A line feed alone moves down without going back to the left edge, and a carriage return alone goes back along the same line. With output redirected to a file the bytes are stored exactly as sent.

Pitfalls

One character per call is slow for long texts: each call goes through the DOS dispatcher, the Ctrl+C check and the CON driver. For strings use INT 21h AH=09h or INT 21h AH=40h. MS-DOS checks the keyboard for a waiting Ctrl+C during this call, so a long printing loop can be stopped by the user; INT 21h AH=06h writes without that check. To bypass DOS altogether (no redirection, a chosen colour or position) there is the BIOS: INT 10h AH=0Eh.