Back to Blog
Python

python bytes vs bytearray: Choosing the Right Binary Type

python bytes vs bytearray: Compare Python's bytes and bytearray types: mutability, memory behavior, performance, and when to use each for binary data handling.

Pythonbytesbytearraybinary datamutable sequences
Side-by-side comparison of Python bytes and bytearray showing immutable vs mutable binary sequences

When you need to work with binary data in Python, the two built-in types bytes and bytearray often appear as interchangeable options. They share a similar interface, but their mutability difference leads to distinct behavior in memory, performance, and practical usage. The choice between python bytes vs bytearray depends on whether you need an immutable, hashable sequence or a mutable buffer that you can modify in place.

What Are bytes and bytearray?

Both bytes and bytearray represent sequences of integers in the range 0–255. The essential difference is that bytes is immutable, while bytearray is mutable. This single property cascades into almost every other difference.

# Creating bytes b = b'hello' # literal b2 = bytes([104, 101, 108, 108, 111]) # from list of ints b3 = bytes(5) # five zero bytes # Creating bytearray ba = bytearray(b'hello') ba2 = bytearray([104, 101, 108, 108, 111]) ba3 = bytearray(5) # five zero bytes

bytes literals use the b prefix. bytearray has no literal syntax; you must call the constructor. Both types support indexing, slicing, and many common sequence operations.

Mutability and Its Consequences

The immutability of bytes means you cannot change an element or slice after creation. Any operation that appears to modify a bytes object actually creates a new object. In contrast, bytearray allows in-place changes via item assignment, slice assignment, and methods like append, extend, and pop.

ba = bytearray(b'hello') ba[0] = 72 # Valid: changes to b'Hello' ba[1:4] = b'EY' # Valid: slice assignment b = b'hello' # b[0] = 72 # TypeError: 'bytes' object does not support item assignment

This mutability affects how you design code. If you need to accumulate binary data piece by piece, bytearray avoids creating many intermediate objects. If you need a stable, hashable value, bytes is the only option because bytearray is unhashable.

Memory and Performance Characteristics

Because bytes is immutable, operations like concatenation or replacement allocate a new object and copy the data. Repeatedly building a large bytes object with += can lead to quadratic time complexity. bytearray can be modified in place, so appending or extending typically amortizes to O(1) per operation, similar to a list.

Memory usage also differs. bytearray may allocate extra capacity to accommodate future growth, similar to a list. bytes uses exactly the required memory. However, bytes objects can be shared safely across threads without locking, while bytearray requires synchronization if mutated concurrently.

The performance advantage of bytearray is most visible when you need to build or modify a buffer incrementally. For example, parsing a network protocol where you read chunks and append them to a buffer benefits from bytearray.

Common Operations and Syntax Differences

Both types support indexing, slicing, iteration, and membership tests. They also have methods like find, startswith, split, and count. However, methods that modify the sequence are only available on bytearray.

Operationbytesbytearray
Item assignmentNot allowedAllowed
Slice assignmentNot allowedAllowed
append, extend, popNot availableAvailable
replaceReturns new bytesReturns new bytearray
hash()HashableUnhashable
memoryview supportRead-onlyMutable

bytes and bytearray can be passed to functions expecting a buffer-like object. memoryview can wrap both, but a memoryview of a bytes object is read-only, while a memoryview of a bytearray is writable.

When to Use bytes vs bytearray

The decision hinges on whether you need mutability. Use bytes when:

  • You need a hashable value, such as a dictionary key or an element in a set.
  • The data is read-only and shared across threads or processes.
  • You are working with immutable data from network packets, file contents, or cryptographic operations.
  • You want to guarantee that the data cannot be accidentally modified.

Use bytearray when:

  • You need to build a binary message incrementally.
  • You are reading from a socket or file and want to accumulate data without creating many intermediate objects.
  • You need to modify specific bytes or slices in place.
  • You are implementing a buffer or a memory pool.

A common pattern is to read from a socket into a bytearray and then convert it to bytes when you need a stable, hashable representation.

Converting Between bytes and bytearray

Conversion between the two is straightforward. bytes(bytearray_obj) creates a new immutable copy. bytearray(bytes_obj) creates a mutable copy. Both conversions copy the underlying data, so they have O(n) cost.

b = b'hello' ba = bytearray(b) # mutable copy b2 = bytes(ba) # immutable copy

When converting, be aware that bytes(ba) returns a new object; it does not share memory. If you need a zero-copy view, use memoryview with the appropriate mutability flag.

Compatibility and Error Handling

Both bytes and bytearray have been part of Python since version 2.6 (as bytes was introduced in Python 3, but bytearray existed earlier). In Python 3, str and bytes are distinct, and mixing them raises TypeError. When working with binary data, ensure you are using bytes or bytearray and not str.

A common pitfall is trying to use a bytearray where a bytes is expected. Some APIs, like hashlib or hmac, require a bytes-like object and accept bytearray, but others may explicitly check for bytes. Always check the documentation for the specific library.

Another issue is that bytearray is mutable, so if you pass it to a function that modifies it, the caller sees the changes. This can lead to subtle bugs. If you need to protect the original data, pass a bytes copy or use bytes(ba).

Practical Example: Building a Binary Protocol Buffer

Suppose you are implementing a simple network protocol where each message starts with a 4-byte length header followed by a payload. You need to accumulate incoming chunks into a buffer and extract complete messages.

buffer = bytearray() def process_chunk(chunk: bytes): buffer.extend(chunk) while len(buffer) >= 4: length = int.from_bytes(buffer[:4], 'big') if len(buffer) < 4 + length: break payload = bytes(buffer[4:4+length]) handle_message(payload) del buffer[:4+length]

Using bytearray lets you extend the buffer in place and delete consumed bytes with del buffer[:...]. If you used bytes, you would have to create a new concatenated object on every chunk, which is inefficient. After extracting the payload, converting it to bytes gives you an immutable object that can be safely passed to other parts of the application or used as a dictionary key.

This example shows how the mutability of bytearray directly supports an efficient incremental parsing pattern. The choice between bytes and bytearray is not about one being better overall; it is about matching the type to the mutability requirements of your algorithm.

python bytes vs bytearray: Choosing the Right Binary Type | RYUSLOG DEV