cswork / libraryOpen practice workspace

Articles

Apache Arrow Interview Questions: Columnar Memory, Zero-Copy Limits, and Pandas Conversion

Practice Apache Arrow interview questions with buffer-level examples of nulls, strings, chunks, zero-copy limits, and pandas dtype conversion.

An Apache Arrow interview answer should begin at the buffer boundary. Arrow specifies a columnar in-memory representation: a fixed-width integer array can keep values in one contiguous buffer; nullability uses a validity bitmap; strings need offsets plus bytes. “Zero-copy” applies only when the consumer can use the existing buffer and representation. Converting Arrow to a conventional NumPy-backed pandas column often allocates—especially for nulls, strings, multiple chunks, or DataFrame block consolidation. The useful interview skill is to predict which step copies and why.

This guide uses the official Arrow columnar format, PyArrow's pandas integration guide, and a locally run PyArrow 21.0.0/pandas 3.0.5 fixture. The prompts and explanations are original practice, not reports of a particular employer's interview. For an adjacent storage-format exercise, Design a Compact Columnar File Format is useful—but an on-disk Parquet-like layout is a different question from Arrow's in-memory buffers.

Question 1: What is physically columnar about Arrow?

Imagine three rows with customer_id, amount, and status. A row-oriented representation might place one customer's three fields together. Arrow's columnar representation keeps values for a given field together in arrays with defined physical layouts. A scan or vectorized operation over amount can access a dense numeric buffer without walking Python objects representing every row. That can improve interoperability and analytical processing, but the mere word “columnar” does not guarantee faster end-to-end execution; conversion, cache behavior, data type, and operation matter.

An Arrow RecordBatch has a schema and same-length arrays; a Table can hold chunked columns. Ask whether the interviewer means one array, a batch, or a table. The distinction matters when someone asserts that a whole table can be viewed as one contiguous NumPy array. Each field has its own buffers, and each chunk may have separate buffers. Projection can select a column without copying its values in many cases, but a later conversion may still copy.

Arrow is an in-memory interchange specification with related IPC formats; Parquet is an on-disk columnar storage format with row groups, encodings, compression, and file metadata. Both can appear in one pipeline: read Parquet, decode into Arrow arrays, transform, convert to pandas. “The read is zero-copy because Parquet and Arrow are columnar” skips decoding and decompression, so it is not a sound general claim.

Question 2: Walk through a nullable string array

Use the logical values ['a', null, 'cat']. In Arrow's ordinary variable-size binary/string layout, a validity bitmap marks the middle slot null; an offsets buffer identifies the byte ranges; a data buffer stores the encoded bytes. One possible conceptual picture uses offsets [0, 1, 1, 4] and the data bytes acat. That is an illustration of a valid layout, not a promise that every producer emits exactly those bytes for a null slot. Arrow's specification allows null slots' underlying value storage to be unspecified; the validity bit controls interpretation.

For a fixed-width int64 column [10, null, 30], the value buffer has three positions, and the bitmap says which positions to read. The middle value-buffer bytes are not “the null value.” They may be arbitrary. A robust consumer checks validity first, then reads valid values. This is a common interview trap: treating null as a sentinel in the data buffer or confusing 0 with missing.

Nested values have additional layers. A list uses offsets into a child array, and a struct has children plus its own top-level validity. You need not recite every Arrow layout from memory, but be ready to draw one array and identify which buffer establishes length, which marks validity, and which contains payload. If a string is empty rather than null, the offsets may be equal while its validity bit is set; equal offsets alone do not imply missingness.

Logical valueValidityOffsets/payload implicationWhat a consumer returns
'a'ValidOne byte between adjacent offsetsString 'a'
nullInvalidRange may be empty or ignoredMissing value
''ValidEqual adjacent offsetsEmpty string, not null
'cat'ValidThree bytes in payloadString 'cat'

That difference matters at a pandas boundary. If an ETL step turns missing into empty strings, counts, joins, and validation can change silently even if memory use looks better.

Question 3: Does slicing an Arrow array copy?

For many Arrow arrays, a slice can share the original buffers and carry a logical offset and length. That is cheap relative to allocating and copying all values. It also means the slice can keep a much larger underlying allocation alive. If you retain a ten-row slice from a huge batch, inspect the actual buffer ownership before assuming the large allocation can be released.

Do not generalize this to every operation. Filtering, casting, combining chunks, or converting a representation can allocate new buffers. “Zero-copy” should always name both source and destination, such as “this non-null int64 Arrow array to this NumPy view,” rather than the entire Arrow-to-pandas pipeline. A useful follow-up is whether the destination can mutate the buffer. Arrow's values are logically immutable; a zero-copy NumPy view from PyArrow is commonly read-only, so code that requires in-place writes may force a separate array even if the initial view shared memory.

Question 4: When can Arrow become a NumPy-backed pandas Series without a copy?

The PyArrow pandas guide's zero-copy section gives a narrow rule for Arrow Array or one-chunk ChunkedArray to NumPy/pandas: supported fixed-width numeric representation, no nulls, and one chunk. Strings use a different layout; nulls use a bitmap that ordinary NumPy numeric dtypes cannot represent; two chunks are not one contiguous NumPy buffer. A timestamp or other type may bring its own conversion details, so verify its actual destination dtype rather than relying on the shortcut.

The distinction between Array.to_numpy(zero_copy_only=True) and Table.to_pandas() matters. A simple one-column array may share its values buffer. Building a pandas DataFrame can consolidate compatible columns into an internal block, allocating new storage. PyArrow documents split_blocks=True as a way to avoid that consolidation and permit zero-copy columns in the narrow cases above. Later pandas operations may consolidate again; this option is not a permanent no-copy guarantee.

Arrow input and destinationExpected copy boundaryWhy
One non-null int64 array → NumPy viewCan share values bufferCompatible fixed-width contiguous representation
Nullable int64 → default NumPy-backed pandasCopy/conversionBitmap must become a destination missing-value representation
Two chunks of int64 → one NumPy arrayCopy to concatenateDestination requires one contiguous array
Two non-null numeric columns → default DataFrameOften copy during consolidationpandas block construction differs from individual array views
Same table with split_blocks=TrueCan share eligible columns initiallyAvoids consolidation, subject to later operations

Question 5: What does zero_copy_only=True prove?

It is a useful assertion at a specific conversion call: fail if that call cannot proceed without copying underlying data. It is not proof that reading the original file, creating the Arrow array, downstream pandas transformations, or serialization were allocation-free. Test it where the claim is made and inspect dtype, chunk count, null count, and buffer identity.

This small fixture was run locally with PyArrow 21.0.0 and pandas 3.0.5. Exception classes or integration details may differ by version; the principles are documented separately in the official guides.

import pyarrow as pa
import pandas as pd

plain = pa.array([10, 20, 30], type=pa.int64())
view = plain.to_numpy(zero_copy_only=True)
assert plain.buffers()[1].address == view.__array_interface__["data"][0]
assert not view.flags.writeable

nullable = pa.array([10, None, 30], type=pa.int64())
assert str(nullable.to_pandas().dtype) == "float64"
assert str(nullable.to_pandas(types_mapper=pd.ArrowDtype).dtype) == "int64[pyarrow]"

chunked = pa.chunked_array([[10, 20], [30]], type=pa.int64())
assert chunked.num_chunks == 2
# nullable.to_numpy(zero_copy_only=True) and
# chunked.to_numpy(zero_copy_only=True) fail in this tested runtime.

The last two calls are left commented so the demonstration runs. Turn them into exception assertions in a local interview exercise and describe why each fails. The important prediction is the copy requirement, not memorizing a Python exception class. Check the result's actual dtype before claiming that null semantics survived unchanged.

Question 6: Does Arrow-backed pandas change the answer?

Yes, it changes the destination representation. pandas supports Arrow-backed extension arrays via ArrowDtype and the dtype_backend="pyarrow" option in several readers. Its PyArrow functionality guide explains that these columns can be backed by a pyarrow.ChunkedArray and preserve Arrow's nullability. That is different from converting to a traditional NumPy int64 column that cannot hold nulls.

Do not turn “Arrow-backed” into “every pandas operation is zero-copy.” A pandas operation may call an Arrow compute kernel that creates a new result, convert to NumPy, or use an implementation path with different memory costs. Inspect the resulting dtype and profile the specific transformation. In the fixture, default conversion of nullable int64 yielded float64, while types_mapper=pd.ArrowDtype yielded int64[pyarrow]; the latter preserves integer nullability. Whether a later operation shares a particular buffer is another question.

If asked to choose a dtype backend, ask what the consumer expects. Does a library require a NumPy array? Will it mutate in place? Are strings and missing integers common? Does serialization need Arrow types? A representation that saves a conversion in one stage can make a different stage more expensive. Show the full boundary map before naming a “best” backend.

Question 7: Diagnose a memory spike during conversion

Original scenario: A table has a nullable identifier, two numeric columns, strings, and dozens of chunks per field. The Arrow table fits in memory, but to_pandas() spikes peak RSS and the job is killed. The team says Arrow promised zero-copy. How do you investigate?

First capture row count, per-column type, null count, number of chunks, and Arrow allocated bytes. Measure process RSS before and during conversion because Arrow's allocator statistics do not cover every pandas or Python allocation. Check whether converting multiple numeric columns consolidates blocks, whether chunk combination copies, and whether strings become Python objects or a different extension dtype. Compare a small representative subset before changing production code.

Then pick a remedy tied to the bottleneck. Project only needed columns and process bounded record batches if full materialization is unnecessary. Consider split_blocks=True when its documented conditions apply, while remembering later pandas operations may consolidate. Arrow documents self_destruct=True as a way to free Arrow memory during conversion in some cases, but it can invalidate use of the input table and has caveats when buffers are shared; do not introduce it as a casual one-line fix. If the consumer supports Arrow-backed pandas, test that path and inspect the result. If it fundamentally needs Python objects, budget for the conversion rather than promising zero-copy.

A convincing answer reports peak memory and correctness after the change: same row count, same null positions, expected dtypes, and a representative output sample. Faster conversion that silently turns identifiers into floats or missing strings into empties is not an improvement.

Sources and further reading