Skip to main content

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

CharacterNameActionPrimary OS Usage
\nLine Feed (LF)Move cursor down one lineLinux, macOS
\rCarriage Return (CR)Move cursor to line startClassic Mac OS (pre-OS X)
\r\nCRLFReturn to start, then new lineWindows

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%
tip

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
note

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 CaseCharacterExample
Standard new lines\nprint("Line 1\nLine 2")
Overwrite current line\rprint(f"\r{status}", end="")
Windows files\r\nNetwork protocols, legacy systems
Cross-platform readingDefaultopen(file, "r") handles all
Progress indicators\rLive 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.