INT 16h AH=01h looks at the head of the keyboard buffer and returns at once. The answer is in the zero flag: ZF = 1 means the buffer is empty, ZF = 0 means a key is there, and then AX holds it exactly as INT 16h AH=00h would return it. The key is not removed: call 01h ten times and you see the same key ten times. To consume it, follow with AH=00h, which then returns immediately.

The example empties the buffer, stores an "X", and peeks at it several times before reading it:

prints: - 2D58 2D58 -
FLUSH1:
MOV AH, 01h ; BIOS: is a key waiting?
INT 16h
JZ EMPTY1 ; ZF = 1: the buffer is empty
MOV AH, 00h ; take the key out
INT 16h
JMP FLUSH1
EMPTY1:
CALL PEEK1 ; empty: prints -
MOV AH, 05h ; store "X": scan 2Dh, ASCII 58h
MOV CX, 2D58h
INT 16h
CALL PEEK1 ; 2D58
CALL PEEK1 ; 2D58 again: 01h leaves the key where it is
MOV AH, 00h ; now take it out
INT 16h
CALL PEEK1 ; empty again: -
PEEK1 PROC NEAR ; INT 16h AH=01h, then print the key or "-"
MOV AH, 01h
INT 16h ; ZF = 1: nothing waiting
JZ NONE1
CALL PHEX4 ; the key, still in the buffer
JMP GAP1
NONE1:
MOV DL, "-"
MOV AH, 02h
INT 21h
GAP1:
MOV DL, " "
MOV AH, 02h
INT 21h
RET
PEEK1 ENDP
PHEX4 PROC NEAR ; print AX as four hex digits; keeps every register
PUSH AX
MOV AL, AH
CALL PHEX2 ; the high byte first
POP AX
CALL PHEX2 ; then the low byte
RET
PHEX4 ENDP
PHEX2 PROC NEAR ; print AL as two hex digits; keeps every register
PUSH AX
PUSH CX
PUSH DX
MOV DH, AL ; keep the byte
MOV CL, 4
SHR AL, CL ; the high nibble
CALL HEXN1
MOV AL, DH
AND AL, 0Fh ; the low nibble
CALL HEXN1
POP DX
POP CX
POP AX
RET
HEXN1:
ADD AL, "0"
CMP AL, "9"
JBE HEXN2
ADD AL, 7 ; past "9": "A".."F"
HEXN2:
MOV DL, AL
MOV AH, 02h
INT 21h
RET
PHEX2 ENDP

The polling loop

This is how a program does its own work and still reacts to keys: check, do one step, check again. The loop below never blocks; when a key turns up it is read and handled, and the work goes on.

MAIN1:
; ... one frame of work: move, draw ...
MOV AH, 01h ; a key?
INT 16h
JZ MAIN1 ; no: carry on
MOV AH, 00h ; yes: take it
INT 16h
CMP AL, 1Bh ; Esc ends the loop
JNE MAIN1

Pitfalls

Test ZF straight after the INT; the BIOS returns it by changing the flags saved on the stack, and any CMP or arithmetic in between destroys the answer. The value in AX when ZF = 1 is undefined: some BIOSes leave AH = 01h, some zero it. Like 00h, this function hides the extended-keyboard keys: in DOSBox-X a waiting F11 or F12 is quietly removed from the buffer and the call reports the key after it, or none. INT 16h AH=11h is the enhanced version. The DOS equivalent is INT 21h AH=0Bh, which also checks for Ctrl+C.