DOS interrupt 21h function 09h is the easiest way to print a string in 8086 assembly: point DX at your string, set AH to 09h, call INT 21h, and you’re done. No loop, no character counter, no BX pointer arithmetic. The tradeoff is a minor convention: the string must end with a $ character so DOS knows where to stop. Compare this with the function 02h character loop approach — 09h is cleaner for fixed strings; 02h gives you more control for dynamic output.
Prerequisites: Basic understanding of 8086 registers and the segment setup pattern. Read 8086 Assembly Program to Display String ‘hello’ first if you want to understand the longer version before the shortcut.
data segment
msg1 db 'hello$' ; $ is the string terminator required by INT 21h function 09h
data ends
code segment
assume cs:code, ds:data
start:
mov ax, data
mov ds, ax
mov sp, 0d00h
lea dx, msg1 ; DX must point to the string before calling function 09h
mov ah, 09h
int 21h ; print the string at DS:DX up to the '$'
mov ax, 4c00h
int 21h
code ends
end start
lea dx, msg1 was missing: The original version of this program omitted the lea dx, msg1 instruction. It appeared to work because DX happened to be 0000h at program startup, and msg1 sits at offset 0000h in the data segment — so DS:DX accidentally pointed to the right place. This is a latent bug: any code that modifies DX before the print call (even setup code in a real program) would send INT 21h chasing the wrong address. The corrected version above always loads DX explicitly.
Related Links:
- 8086 Assembly Program to Display String ‘hello’ — the function 02h loop version
- 8086 Assembly Program to Search an Element in an Array
How it works
The data segment holds msg1 db 'hello$'. The $ is not printed — it’s a sentinel that tells DOS’s print routine to stop. Without it, DOS would keep reading bytes past the end of the string until it stumbled across a $ somewhere in memory, printing whatever garbage it found along the way.
In the code segment, after DS is initialized, lea dx, msg1 loads the effective address of the string into DX. Then mov ah, 09h selects the print-string function, and int 21h does the work. That’s the entire print call — three instructions.
Flowchart

Output
C:TASM>debug an_hello.exe -g hello Program terminated normally -q
emu8086 version
Tested with: emu8086 v4.08, Windows 10.
; emu8086 -- print 'hello' using INT 21h function 09h
#make_COM#
org 100h
start:
lea dx, msg1 ; DX = address of string
mov ah, 09h ; function 09h: print string at DS:DX up to '$'
int 21h
mov ax, 4c00h
int 21h
; --- Data ---
msg1 db 'hello$'
NASM version
Tested with: NASM 2.16.01, DOSBox 0.74-3.
; NASM -- print 'hello' using INT 21h function 09h
; nasm -f bin hello09.asm -o hello09.com
bits 16
org 100h
start:
mov dx, msg1 ; DX = offset of string (NASM: address without brackets)
mov ah, 09h
int 21h
mov ax, 4c00h
int 21h
; --- Data ---
msg1 db 'hello$'
In NASM, mov dx, msg1 loads the offset of msg1 into DX — this is correct for function 09h, which expects an offset in DX. You do not want mov dx, [msg1] here; that would dereference the address and load the first two bytes of the string (‘he’ as a word) instead of the address itself.
Frequently Asked Questions
Why does the string need a $ at the end?
Function 09h does not accept a length argument. It reads bytes from DS:DX and prints each one until it reads a $ (ASCII 24h). If the $ is missing, DOS keeps printing bytes beyond the end of your string — whatever happens to be in memory at that point. The $ is not printed and does not appear on screen.
Can I print a string that contains a $ character?
Not with function 09h — the first $ encountered terminates output. If your string must contain a literal dollar sign, use the character-by-character function 02h loop approach (described in this post), which uses a counter instead of a terminator and can print any byte value.
What’s the difference between lea dx, msg1 and mov dx, offset msg1?
They produce the same result: DX gets the offset address of msg1. LEA (Load Effective Address) computes the address at runtime and can handle complex address expressions. mov dx, offset msg1 is a MASM-specific directive that resolves the offset at assembly time. For simple variable names they’re interchangeable; LEA is more flexible for indexed or indirect addressing.
Conclusion
Function 09h reduces string printing to three instructions: load DX with the string address, set AH to 09h, call INT 21h. The only discipline it requires is the $ terminator — forget it and DOS prints garbage past the end of your string. Always load DX explicitly with lea dx, msg1 rather than relying on DX happening to be zero at startup. With those two habits in place, function 09h is the cleanest way to print fixed strings in 8086 DOS assembly.
See Also
- 8086 Assembly Program to Display String ‘hello’ — function 02h character-by-character version
- 8086 Assembly Program to Search an Element in an Array — uses the PRINT macro with function 09h
- Understanding DW and DB in 8086 Assembly