bytes.zfill()

Zero-padding that knows about signs. The sign-aware rule is what separates it from rjust with a zero fill — and also what makes it do odd things to non-numeric data.

Bytes methodPython 3.0+Live demo
Common call
data.zfill(8)
Returns
a new bytes object at least width long
Replaces
data.rjust(width, b'0'), which would put zeros BEFORE a minus sign
Watch out
it pads any bytes, not just digits — b"ab".zfill(4) is b"00ab"
bytes.zfill(widthwidth — Minimum total length. If the data is already this long or longer it is returned unchanged.type: int · required)
→ bytes

Demo

Live evaluation
Try:
Inputs
sstrdata (encoded as utf-8)
widthintminimum width
Output
bytes('42', 'utf-8').zfill(5)
b'00042'

Zeros are inserted on the left until the buffer reaches the width. The sign cases show the rule that matters: a leading minus or plus stays in FRONT, with the zeros after it, so -42 becomes -0042 rather than 00-42. Data already at or beyond the width is returned untouched — it never truncates. And it pads anything at all, not just digits, which is how ab becomes 00ab.

Parameters

NameTypeRequiredDescription
widthintyesMinimum total length. If the data is already this long or longer it is returned unchanged.

Return value

bytes — A new bytes object left-padded with 0x30 bytes to at least width. A leading + or - stays at the front, ahead of the zeros. Never truncates.

Common patterns

Fixed-width numeric field
Legacy formats often want zero-padded numbers as bytes.
field = str(n).encode().zfill(8)
Prefer format for new code
Formatting the int directly is clearer and handles the sign the same way.
field = f'{n:08d}'.encode()
Pad a hex digest to a fixed width
Leading zeros are significant in hex and easy to lose.
digest_hex = value.hex().encode().zfill(64)

Examples

1. Digits
b'42'.zfill(5)
Returns
b'00042'
2. Negative
b'-42'.zfill(5)
Returns
b'-0042'
3. Plus
b'+42'.zfill(5)
Returns
b'+0042'
4. Already wide
b'123456'.zfill(3)
Returns
b'123456'
5. Non-numeric
b'ab'.zfill(4)
Returns
b'00ab'
6. rjust differs
b'-42'.rjust(5, b'0')
Returns
b'00-42' # sign buried

Pitfalls

1. It pads non-numeric data too
zfill does not check that the bytes are digits. Applied to arbitrary data it happily prepends zeros, producing nonsense that looks like a number.
Looks numeric
b'ab'.zfill(4)
b'00ab'
Validate first
if data.lstrip(b'+-').isdigit():
    data = data.zfill(4)
only real numbers
2. rjust with a zero fill is not the same
rjust puts the fill before everything, including a sign. zfill keeps the sign in front. For negative numbers the two produce different results.
Sign buried
b'-42'.rjust(5, b'0')
b'00-42'
zfill keeps it
b'-42'.zfill(5)
b'-0042'
3. Only a single leading sign is recognised
A sign anywhere but the very first byte is just another byte, and gets zeros in front of it like anything else.
Not a sign
b'4-2'.zfill(5)
b'004-2'
Sign must lead
b'-42'.zfill(5)
b'-0042'

When to use

Use it
  • Fixed-width numeric fields in legacy binary or text formats
  • Zero-padding hex where leading zeros are significant
  • Data that is already bytes and already numeric
Reach for something else
  • Formatting a number you hold as an int → an f-string with :0Nd
  • Arbitrary data — it will pad anything
  • Padding with a byte other than zero → rjust

Notes

Complexity
O(width) — builds the padded result in one allocation
Return
A new bytes object; the original is returned when already wide enough
CPython impl
Objects/bytesobject.c :: stringlib_zfill
Memory
Allocates a buffer of max(len, width)
Thread-safe
Yes — bytes are immutable

FAQ

Because zfill is designed for numbers, and -0042 is a valid zero-padded number while 00-42 is not. It checks the first byte for + or - and inserts the zeros after it.

b'-42'.zfill(5)
# b'-0042'

History

3.0
bytes.zfill arrived with the bytes type in the text/binary split.