The str.zfill(width) method pads a string on the left with ASCII '0' digits until the total string length reaches the specified width.
The Sign-Aware Invariant: If the string begins with a leading sign character ('+' or '-'), zfill() inserts the padding zeros after the sign prefix rather than before it. This preserves valid numeric representation (e.g., "-42".zfill(5) yields "-0042"), unlike generic right-justification tools which blindly corrupt signs.
The standard CPython signature for str.zfill() is:
str.zfill(width)
| Parameter | Type | Default | Description |
|---|---|---|---|
width |
int |
Required | The target total width of the resulting string. If width is less than or equal to len(s), the original string is returned intact without truncation. |
zfill() is a string method. Calling 42.zfill(5) directly raises an AttributeError: 'int' object has no attribute 'zfill'. Convert numbers to strings first via str(val).
Padding starts from the leftmost character and stops once the output string matches the requested width:
# Padding standard integers converted to strings
record_id = "84"
padded_id = record_id.zfill(6)
print("Original ID: ", repr(record_id))
print("Padded ID: ", repr(padded_id))
# Width smaller than original length: NO truncation
long_id = "987654321"
unchanged = long_id.zfill(4)
print("Unchanged: ", repr(unchanged))
Expected Output:
Original ID: '84'
Padded ID: '000084'
Unchanged: '987654321'
Key behavioral guarantees:
width, Python never truncates the string; it returns an identical copy.0 or negative integers for width simply returns the original string.zfill() works on arbitrary text: "code".zfill(8) produces "0000code".The defining technical distinction of zfill() is its specialized handling of leading arithmetic sign characters ('+' and '-'):
positive_val = "+42"
negative_val = "-42"
# Zeros are placed AFTER the sign character
print("Positive zfill: ", positive_val.zfill(6))
print("Negative zfill: ", negative_val.zfill(6))
Expected Output:
Positive zfill: +00042
Negative zfill: -00042
Understanding when to use zfill() versus generic alignment methods prevents subtle numeric parsing bugs:
val = "-50"
# FLAWED: rjust() places zeros BEFORE the negative sign
corrupted = val.rjust(6, "0")
print("rjust(6, '0'): ", corrupted) # Produces '000-50' (Invalid numeric syntax!)
# ROBUST: zfill() preserves sign syntax
valid = val.zfill(6)
print("zfill(6): ", valid) # Produces '-00050' (Valid integer format!)
Expected Output:
rjust(6, '0'): 000-50
zfill(6): -00050
| Feature | str.zfill(width) |
str.rjust(width, '0') |
f"{val:0{width}d}" |
|---|---|---|---|
| Sign-Aware Padding | Yes (-00042) |
No (000-42) |
Yes (-00042) |
| Requires String Type | Yes (calls on str) |
Yes (calls on str) |
No (accepts raw int directly) |
| Custom Fill Character | No (always ASCII '0') |
Yes (any character) | Yes (in format specifier) |
| Truncation on Overflow | Never truncates | Never truncates | Never truncates |
Generating uniform 8-character employee codes from sequential database primary keys:
def generate_badge_id(dept_code: str, employee_num: int) -> str:
# 5-digit zero-padded number preceded by 3-character dept code
padded_num = str(employee_num).zfill(5)
return f"{dept_code.upper()}-{padded_num}"
print("Early employee:", generate_badge_id("eng", 7))
print("Mid employee: ", generate_badge_id("eng", 482))
print("Late employee: ", generate_badge_id("sales", 12903))
Expected Output:
Early employee: ENG-00007
Mid employee: ENG-00482
Late employee: SALES-12903
Constructing ISO-compliant timestamp strings from raw numeric components without external datetime dependencies:
def format_iso_timestamp(year: int, month: int, day: int, hour: int, minute: int, second: int) -> str:
y = str(year).zfill(4)
m = str(month).zfill(2)
d = str(day).zfill(2)
h = str(hour).zfill(2)
mi = str(minute).zfill(2)
s = str(second).zfill(2)
return f"{y}-{m}-{d}T{h}:{mi}:{s}Z"
print("ISO Timestamp:", format_iso_timestamp(2026, 4, 9, 8, 5, 2))
Expected Output:
ISO Timestamp: 2026-04-09T08:05:02Z
Formatting signed deltas with fixed-width visual alignment in telemetry displays:
def format_signed_delta(delta: float, width: int = 8) -> str:
prefix = "+" if delta > 0 else ""
raw_str = f"{prefix}{delta:.2f}"
return raw_str.zfill(width)
print("Gain: ", format_signed_delta(14.50))
print("Loss: ", format_signed_delta(-8.25))
print("Flat: ", format_signed_delta(0.00))
Expected Output:
Gain: +0014.50
Loss: -0008.25
Flat: 00000.00
When communicating with microcontrollers, CAN bus systems, or network hardware protocols, values must often be encoded as fixed-width hexadecimal memory addresses without truncation:
def format_hex_register(value: int, byte_width: int = 4) -> str:
# Strip Python "0x" prefix and pad with leading zeros
hex_digits = hex(value)[2:].upper()
return "0x" + hex_digits.zfill(byte_width)
print("Register 0:", format_hex_register(0x1F, 4))
print("Register 1:", format_hex_register(0x3, 4))
print("Memory 32:", format_hex_register(0xA4C, 8))
Expected Output:
Register 0: 0x001F
Register 1: 0x0003
Memory 32: 0x00000A4C
Python's zfill() only recognizes '+' or '-' if it is the very first character of the string. Signs located inside the string are treated as normal characters:
val = "1-2"
print(val.zfill(6)) # Produces '0001-2', NOT '1-0002'
Attempting 123.zfill(5) is a syntax and attribute error. Always call str(val).zfill(5) or use modern formatted f-strings f"{val:05d}".
Below is a production-grade utility class, ZeroPadder, demonstrating badge generation, ISO timestamp padding, and automated unit assertions:
class ZeroPadder:
@staticmethod
def format_badge(prefix: str, sequence_id: int, width: int = 6) -> str:
"""Creates uniform badge identifiers."""
return f"{prefix}-{str(sequence_id).zfill(width)}"
@staticmethod
def pad_signed_value(value: int | float, total_width: int) -> str:
"""Pads signed numbers preserving positive/negative prefix."""
s = f"+{value}" if value > 0 else str(value)
return s.zfill(total_width)
@staticmethod
def pad_time_unit(unit: int) -> str:
"""Formats 2-digit date/time units."""
return str(unit).zfill(2)
# ==========================================
# SELF-CHECKING UNIT ASSERTIONS
# ==========================================
padder = ZeroPadder()
# 1. Basic zero padding
assert "42".zfill(5) == "00042"
assert "12345".zfill(3) == "12345" # Overflow: no truncation
assert "".zfill(4) == "0000"
# 2. Signed zero padding
assert "-42".zfill(6) == "-00042"
assert "+42".zfill(6) == "+00042"
# 3. Contrast with rjust (demonstrating why zfill is safer for numbers)
assert "-42".rjust(6, "0") == "000-42" # rjust corrupts sign placement
# 4. Badge formatting
assert padder.format_badge("USR", 125, 6) == "USR-000125"
# 5. Time unit padding
assert padder.pad_time_unit(5) == "05"
assert padder.pad_time_unit(12) == "12"
# 6. Signed value padding
assert padder.pad_signed_value(25, 6) == "+00025"
assert padder.pad_signed_value(-50, 6) == "-00050"
print("Formatted badge: ", padder.format_badge("EMP", 42))
print("Signed positive: ", padder.pad_signed_value(99, 7))
print("Signed negative: ", padder.pad_signed_value(-99, 7))
print("All str.zfill() unit assertions passed successfully!")
Open in Google Colab View on GitHub
Run all zero-padding examples interactively, contrast zfill() with rjust(), test sign preservation with negative and positive values, and construct ISO timestamps in Google Colab.
Author & Instructor at plus2net
I write and maintain practical tutorials on Python, PHP, SQL, JavaScript, HTML, jQuery, and web development at plus2net. The tutorials focus on clear explanations, working examples, and code that readers can test and adapt while learning.