bitarray review
bitarray 3.10.1 is a Python sequence that packs eight Boolean values into each byte and runs its core operations in compiled C. It behaves enough like a list to support indexing, slices, deletion, concatenation, and mutation, then adds bitwise operators, buffer access, prefix-code helpers, integer conversion, sparse encoding, and hashable frozenbitarray values. Our Python 3.12 install imported in 0.05 seconds and shipped py.typed with no direct dependencies. Version 3.10.1 fixes narrow-width pretty printing and simplifies integer conversion internals; the preceding 3.10.0 release added free-threaded CPython 3.14 support and public decode-tree inspection.
bitarray 3.10.1 installed in 0.2 seconds, used 2 MB, imported in 0.05 seconds, and produced 0 audit findings in our sandbox, making it an easy dependency for dense mutable bits. Walk away when the data is sparse, the format needs named fields, or you cannot store bit endianness and exact length with serialized bytes.
We installed it
| Install | ✓ · 0.2s | 1 package on disk · 2 MB |
| Import | ✓ | import bitarray in 0.05s · compiled extensions · py.typed · requires Python >=3.7 |
| Known vulns | 0 | (pip-audit) |
Answers from our run
Does bitarray install cleanly?
Yes. In a fresh container with an empty cache, pip install bitarray finished in 0.2s, leaving 1 package and 2 MB on disk. pip-audit reported no known vulnerabilities.
What does bitarray need to run?
Python >=3.7, and a platform wheel with compiled extensions. In our run import bitarray succeeded in 0.05s, and the package ships py.typed for type checkers.
bitarray or bitstring: which should you use?
bitstring: Use it when readable binary parsing, bit-position cursors, and mixed-width field interpretation matter more than a minimal mutable bitmap. bitarray 3.10.1 installed in 0.2 seconds, used 2 MB, imported in 0.05 seconds, and produced 0 audit findings in our sandbox, making it an easy dependency for dense mutable bits.
When should you not use bitarray?
The set bits are sparse across a huge integer range. PyRoaring stores compressed integer sets without allocating every zero between the smallest and largest member.
Use it if
- A dense flag vector or bitmap needs one stored bit per value and fast count, search, mask, shift, union, or intersection operations.
- Binary data must move through bytes, files, memory maps, memoryview, or another object that implements Python's buffer protocol.
- Prefix-code work needs Huffman code construction, reusable decode trees, or canonical decoding without a separate codec package.
- Immutable bit sequences need to act as dictionary keys through frozenbitarray while mutable working copies use the same operation model.
- The set bits are sparse across a huge integer range. PyRoaring stores compressed integer sets without allocating every zero between the smallest and largest member.
- Your binary layout has named integers, floats, padding, and mixed field widths. bitstruct or bitstring describes those fields directly; bitarray leaves the slice offsets to your code.
- Native extensions are forbidden in the deployment target. The README says all functionality is implemented in C, and our installed wheel contained compiled .so files.
- The file format cannot carry bit endianness and logical length. tobytes() pads the final byte with zeros, while frombytes() appends all 8 bits from every byte.
- You need multidimensional Boolean computation, broadcasting, or numerical reductions across axes. bitarray is a one-dimensional bit sequence rather than a NumPy replacement.
Setup reality
Our fresh Python 3.12 sandbox installed bitarray 3.10.1 in 0.2 seconds. One package occupied 2 MB on disk, import completed in 0.05 seconds, and pip-audit found 0 known vulnerabilities. It has 0 direct dependencies, requires Python 3.7 or newer, includes py.typed, and uses compiled .so extensions under the PSF-2.0 license. PyPI supplies wheels for the listed CPython platforms; an unlisted target falls back to a C build.
No credentials or config file are involved. The setup choice is representation: every array uses big or little bit endianness, with big as the default. That setting controls how byte, file, buffer, hex, and integer conversions interpret bits. Machine byte order is a separate concern. Record the chosen bit endianness beside persisted data instead of relying on a process default.
tobytes() zero-fills unused positions in the last byte, and frombytes() restores 8 bits for each input byte. A 13-bit value therefore cannot recover its original length from raw bytes alone. serialize() and deserialize() retain the library's bitarray representation when both endpoints use this package. search() and decode() return iterators in version 3, so wrap them with list() only when materializing every result is intentional.
A bitarray imported from another buffer can be read-only, and exported buffers can prevent resizing while a view exists. Bitwise operands need equal lengths. Shifts keep the original length, fill vacated positions with zero, and turn the whole array to zero when the shift count reaches its length. Free-threaded CPython support arrived in 3.10.0, but shared mutation still needs an application-level ownership rule if several threads can change the same array.
Patterns
Build and edit a bit sequence create-and-mutate
from bitarray import bitarray
bits = bitarray('1001 011')
bits.append(0)
bits[1:4] = bitarray('111')
bits[5:] = FalseWhitespace is ignored in a string initializer. Slice assignment with one Boolean fills the selected positions without creating a temporary bitarray.
Intersect and invert equal-length masks combine-masks
from bitarray import bitarray
allowed = bitarray('11010100')
selected = bitarray('10111100')
visible = allowed & selected
hidden = ~visibleBitwise operands must have the same length. Inversion and shifts keep that length rather than adding positions.
Iterate over active indices find-set-bits
from bitarray import bitarray
flags = bitarray('00101001')
for index in flags.search(1):
print(index)search() returns an iterator in version 3. Searching for 1 is the documented fast path for active indices.
Round-trip a byte-aligned value convert-bytes
from bitarray import bitarray
bits = bitarray('10110010', endian='big')
raw = bits.tobytes()
copy = bitarray(endian='big')
copy.frombytes(raw)
assert copy == bitsThis round trip is exact because the input has 8 bits. Raw bytes do not preserve the logical length of a partial final byte.
Preserve bitarray representation serialize-with-metadata
from bitarray import bitarray
from bitarray.util import deserialize, serialize
bits = bitarray('10101', endian='little')
payload = serialize(bits)
restored = deserialize(payload)
assert restored == bits
assert restored.endian == 'little'serialize() records the package representation, including the bit details needed by deserialize(). Use a documented application format for cross-language storage.
Encode and decode a fixed-width integer convert-integer
from bitarray.util import ba2int, int2ba
bits = int2ba(37, length=8, endian='big')
assert bits.to01() == '00100101'
assert ba2int(bits) == 37length fixes the field width and raises when the integer does not fit. Version 3.10.1 simplified the conversion internals without changing this public call shape.
Use immutable bits as a dictionary key use-hashable-bits
from bitarray import frozenbitarray
opcode = frozenbitarray('1100011')
handlers = {opcode: 'load'}
assert handlers[frozenbitarray('1100011')] == 'load'frozenbitarray is immutable and hashable. A mutating operation on it raises TypeError.
View an existing byte buffer map-existing-buffer
from bitarray import bitarray
raw = bytearray([0b10100000])
bits = bitarray(endian='big', buffer=raw)
bits[1] = 1
assert raw[0] == 0b11100000The bitarray imports the supplied buffer instead of copying it. Mutations are shared, and a read-only source produces a read-only view.
Encode and decode symbols encode-prefix-code
from bitarray import bitarray, decodetree
code = {
'A': bitarray('0'),
'B': bitarray('10'),
'C': bitarray('11'),
}
encoded = bitarray()
encoded.encode(code, 'ABCA')
text = ''.join(encoded.decode(decodetree(code)))
assert text == 'ABCA'decode() returns an iterator. A reusable decodetree avoids rebuilding the prefix tree for repeated payloads.
Rotate a sequence in place rotate-bits
from bitarray import bitarray
bits = bitarray('10010')
bits.rotate(2)
assert bits == bitarray('10100')rotate() was added in version 3.9 and mutates the array in place. Positive values rotate to the right.
Encode a sparse bitarray compress-sparse-bits
from bitarray import bitarray
from bitarray.util import sc_decode, sc_encode
bits = bitarray(10_000)
bits.setall(0)
bits[[3, 900, 9_999]] = 1
encoded = sc_encode(bits)
assert sc_decode(iter(encoded)) == bitssc_encode() targets sparse bitarrays. Benchmark it against the real density and storage format before adopting it as a wire contract.
Run the package self-test validate-install
import bitarray
result = bitarray.test()
assert result.wasSuccessful()test() is documented as public API and returns unittest.runner.TextTestResult. It is useful after a source build or on an unusual architecture.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| bitstring | PyPI | Use it when readable binary parsing, bit-position cursors, and mixed-width field interpretation matter more than a minimal mutable bitmap. |
| bitstruct | PyPI | Use it when a struct-style format string should define signed integers, floats, padding, and byte or bit order. |
| numpy | PyPI | Use it for multidimensional Boolean arrays, broadcasting, axis operations, and integration with numerical code. |
| pyroaring | PyPI | Use it for sparse integer sets that benefit from compressed bitmap containers and set-oriented iteration. |
More data guides
numpy · fsspec · pandas · sqlalchemy · pyarrow · lxml · the whole shelf →
How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.

