How to Understand \n vs \r in Python: Newline vs Carriage Return
These two escape sequences control cursor positioning differently: \n moves the cursor down to a new line, while \r returns it to the beginning of the current line. Understanding this distinction is essential for cross-platform compatibility and terminal manipulation.
Core Differences
| Character | Name | Action | Primary OS Usage |
|---|---|---|---|
\n | Line Feed (LF) | Move cursor down one line | Linux, macOS |
\r | Carriage Return (CR) | Move cursor to line start | Classic Mac OS (pre-OS X) |
\r\n | CRLF | Return to start, then new line | Windows |
The terminology comes from typewriters: the carriage return moved the typing mechanism back to the left margin, while the line feed advanced the paper up one line.
Visualizing the Difference
# \n moves to a new line
print("Line 1\nLine 2\nLine 3")
# Output:
# Line 1
# Line 2
# Line 3
# \r returns to the start of the current line
print("Hello\rWorld")
# Output: World
# "World" overwrites "Hello" because cursor returned to start
Output:
Line 1
Line 2
Line 3
World
Character-by-Character Breakdown
text = "ABC\rXY"
# Step by step:
# 1. Print 'A' -> cursor at position 1
# 2. Print 'B' -> cursor at position 2
# 3. Print 'C' -> cursor at position 3
# 4. '\r' -> cursor returns to position 0
# 5. Print 'X' -> overwrites 'A', cursor at position 1
# 6. Print 'Y' -> overwrites 'B', cursor at position 2
# Final visible output: "XYC"
print(text) # XYC
Output:
XYC
The \r Trick: Dynamic Terminal Updates
The carriage return enables in-place updates, perfect for progress indicators and status displays.
Simple Progress Bar
import time
for i in range(1, 6):
print(f"\rProgress: {i * 20}%", end="", flush=True)
time.sleep(0.5)
print("\nComplete!")
Output (animated, updating in place):
Progress: 100%
Complete!
Visual Progress Bar
import time
def progress_bar(current, total, width=40):
percent = current / total
filled = int(width * percent)
bar = '█' * filled + '░' * (width - filled)
print(f"\r[{bar}] {percent:.1%}", end="", flush=True)
for i in range(101):
progress_bar(i, 100)
time.sleep(0.03)
print() # Move to new line when done
Output (the progress bar updates from 0% to 100%):
[████████████████████████████████████████] 100.0%
Always include flush=True when using \r for live updates. Without it, output may be buffered and not display immediately.
Countdown Timer
import time
for seconds in range(10, 0, -1):
print(f"\rStarting in {seconds} seconds... ", end="", flush=True)
time.sleep(1)
print("\rLaunching now! ") # Extra spaces clear previous text
When overwriting with shorter text, add trailing spaces to clear remnants of the previous output.
Cross-Platform File Handling
Different operating systems use different line ending conventions. Python handles this automatically in text mode.
Automatic Normalization
# Python converts all line endings to \n when reading
with open("data.txt", "r") as f:
content = f.read()
# \r\n and \r are converted to \n
# When writing, Python uses the OS-appropriate ending
with open("output.txt", "w") as f:
f.write("Line 1\nLine 2")
# Windows: writes \r\n
# Unix/Mac: writes \n
Preserving Original Line Endings
# newline='' prevents any conversion
with open("data.txt", "r", newline='') as f:
content = f.read()
# Line endings preserved exactly as in file
Forcing Specific Line Endings
# Force Unix-style endings regardless of OS
with open("unix_file.txt", "w", newline='\n') as f:
f.write("Line 1\nLine 2")
# Force Windows-style endings regardless of OS
with open("windows_file.txt", "w", newline='\r\n') as f:
f.write("Line 1\nLine 2")
Converting Between Line Endings
def convert_line_endings(text, target='unix'):
"""Convert line endings to specified format."""
# First normalize to \n
normalized = text.replace('\r\n', '\n').replace('\r', '\n')
if target == 'unix':
return normalized
elif target == 'windows':
return normalized.replace('\n', '\r\n')
elif target == 'classic_mac':
return normalized.replace('\n', '\r')
else:
raise ValueError(f"Unknown target: {target}")
text = "Line 1\r\nLine 2\rLine 3\n"
print(repr(convert_line_endings(text, 'unix')))
# 'Line 1\nLine 2\nLine 3\n'
print(repr(convert_line_endings(text, 'windows')))
# 'Line 1\r\nLine 2\r\nLine 3\r\n'
Detecting Line Ending Style
def detect_line_ending(text):
"""Detect the predominant line ending style."""
crlf_count = text.count('\r\n')
cr_count = text.count('\r') - crlf_count # Exclude \r in \r\n
lf_count = text.count('\n') - crlf_count # Exclude \n in \r\n
counts = {
'Windows (CRLF)': crlf_count,
'Unix (LF)': lf_count,
'Classic Mac (CR)': cr_count
}
if max(counts.values()) == 0:
return "No line endings found"
return max(counts, key=counts.get)
# Test
print(detect_line_ending("line1\r\nline2\r\n")) # Windows (CRLF)
print(detect_line_ending("line1\nline2\n")) # Unix (LF)
Network Protocols and \r\n
Many internet protocols require CRLF line endings:
# HTTP headers use \r\n
http_request = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"
# SMTP email protocol
smtp_message = "MAIL FROM:<sender@example.com>\r\n"
# When working with sockets
def send_line(socket, message):
socket.send(f"{message}\r\n".encode())
Spinner Animation
Another practical use of \r:
import time
import itertools
def spinner(duration=3):
"""Display a spinning animation."""
frames = itertools.cycle(['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'])
end_time = time.time() + duration
while time.time() < end_time:
print(f"\r{next(frames)} Loading...", end="", flush=True)
time.sleep(0.1)
print("\r✓ Complete! ")
spinner(2)
Summary Table
| Use Case | Character | Example |
|---|---|---|
| Standard new lines | \n | print("Line 1\nLine 2") |
| Overwrite current line | \r | print(f"\r{status}", end="") |
| Windows files | \r\n | Network protocols, legacy systems |
| Cross-platform reading | Default | open(file, "r") handles all |
| Progress indicators | \r | Live updating status bars |
Use \n for nearly all standard text output. Reserve \r for terminal tricks like progress bars and spinners where you want to update content in place without scrolling.