From c740883b9eea5cf5a5706b4a1fb931565a5fe3ab Mon Sep 17 00:00:00 2001
From: Neil Conway <neil.conway@gmail.com>
Date: Fri, 20 Feb 2026 16:25:15 -0500
Subject: [PATCH 1/9] Add bench

---
 datafusion/common/Cargo.toml                 |   4 +
 datafusion/common/benches/scalar_to_array.rs | 107 +++++++++++++++++++
 2 files changed, 111 insertions(+)
 create mode 100644 datafusion/common/benches/scalar_to_array.rs

diff --git a/datafusion/common/Cargo.toml b/datafusion/common/Cargo.toml
index 82e7aafcee2b..e4ba71e45c66 100644
--- a/datafusion/common/Cargo.toml
+++ b/datafusion/common/Cargo.toml
@@ -57,6 +57,10 @@ sql = ["sqlparser"]
 harness = false
 name = "with_hashes"
 
+[[bench]]
+harness = false
+name = "scalar_to_array"
+
 [dependencies]
 ahash = { workspace = true }
 apache-avro = { workspace = true, features = [
diff --git a/datafusion/common/benches/scalar_to_array.rs b/datafusion/common/benches/scalar_to_array.rs
new file mode 100644
index 000000000000..8ef836150f1f
--- /dev/null
+++ b/datafusion/common/benches/scalar_to_array.rs
@@ -0,0 +1,107 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+//! Benchmarks for `ScalarValue::to_array_of_size`, focusing on List
+//! scalars.
+
+use arrow::array::{Array, ArrayRef, AsArray, StringViewBuilder};
+use arrow::datatypes::{DataType, Field};
+use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main};
+use datafusion_common::ScalarValue;
+use datafusion_common::utils::SingleRowListArrayBuilder;
+use std::sync::Arc;
+
+/// Build a `ScalarValue::List` of `num_elements` Utf8View strings whose
+/// inner StringViewArray has `num_buffers` data buffers.
+fn make_list_scalar(num_elements: usize, num_buffers: usize) -> ScalarValue {
+    let elements_per_buffer = (num_elements + num_buffers - 1) / num_buffers;
+
+    let mut small_arrays: Vec<ArrayRef> = Vec::new();
+    let mut remaining = num_elements;
+    for buf_idx in 0..num_buffers {
+        let count = remaining.min(elements_per_buffer);
+        if count == 0 {
+            break;
+        }
+        let start = buf_idx * elements_per_buffer;
+        let mut builder = StringViewBuilder::with_capacity(count);
+        for i in start..start + count {
+            builder.append_value(format!("{i:024x}"));
+        }
+        small_arrays.push(Arc::new(builder.finish()) as ArrayRef);
+        remaining -= count;
+    }
+
+    let refs: Vec<&dyn Array> = small_arrays.iter().map(|a| a.as_ref()).collect();
+    let concated = arrow::compute::concat(&refs).unwrap();
+
+    let list_array = SingleRowListArrayBuilder::new(concated)
+        .with_field(&Field::new_list_field(DataType::Utf8View, true))
+        .build_list_array();
+    ScalarValue::List(Arc::new(list_array))
+}
+
+/// We want to measure the cost of doing the conversion and then also accessing
+/// the results, to model what would happen during query evaluation.
+fn consume_list_array(arr: &ArrayRef) {
+    let list_arr = arr.as_list::<i32>();
+    let mut total_len: usize = 0;
+    for i in 0..list_arr.len() {
+        let inner = list_arr.value(i);
+        let sv = inner.as_string_view();
+        for j in 0..sv.len() {
+            total_len += sv.value(j).len();
+        }
+    }
+    std::hint::black_box(total_len);
+}
+
+fn bench_list_to_array_of_size(c: &mut Criterion) {
+    let mut group = c.benchmark_group("list_to_array_of_size");
+
+    let num_elements = 1245;
+    let scalar_1buf = make_list_scalar(num_elements, 1);
+    let scalar_50buf = make_list_scalar(num_elements, 50);
+
+    for batch_size in [256, 1024] {
+        group.bench_with_input(
+            BenchmarkId::new("1_buffer", batch_size),
+            &batch_size,
+            |b, &sz| {
+                b.iter(|| {
+                    let arr = scalar_1buf.to_array_of_size(sz).unwrap();
+                    consume_list_array(&arr);
+                });
+            },
+        );
+        group.bench_with_input(
+            BenchmarkId::new("50_buffers", batch_size),
+            &batch_size,
+            |b, &sz| {
+                b.iter(|| {
+                    let arr = scalar_50buf.to_array_of_size(sz).unwrap();
+                    consume_list_array(&arr);
+                });
+            },
+        );
+    }
+
+    group.finish();
+}
+
+criterion_group!(benches, bench_list_to_array_of_size);
+criterion_main!(benches);

From e6cddaa41ce59e2a038b6a905b5a155faa92d5e1 Mon Sep 17 00:00:00 2001
From: Neil Conway <neil.conway@gmail.com>
Date: Fri, 20 Feb 2026 16:27:56 -0500
Subject: [PATCH 2/9] Add fix

---
 datafusion/common/src/scalar/mod.rs | 28 +++++++++++++++++++++-------
 1 file changed, 21 insertions(+), 7 deletions(-)

diff --git a/datafusion/common/src/scalar/mod.rs b/datafusion/common/src/scalar/mod.rs
index 644916d7891c..32684056ae2e 100644
--- a/datafusion/common/src/scalar/mod.rs
+++ b/datafusion/common/src/scalar/mod.rs
@@ -3008,7 +3008,7 @@ impl ScalarValue {
     ///
     /// Errors if `self` is
     /// - a decimal that fails be converted to a decimal array of size
-    /// - a `FixedsizeList` that fails to be concatenated into an array of size
+    /// - a `FixedSizeList` that fails to be concatenated into an array of size
     /// - a `List` that fails to be concatenated into an array of size
     /// - a `Dictionary` that fails be converted to a dictionary array of size
     pub fn to_array_of_size(&self, size: usize) -> Result<ArrayRef> {
@@ -3435,12 +3435,26 @@ impl ScalarValue {
     }
 
     fn list_to_array_of_size(arr: &dyn Array, size: usize) -> Result<ArrayRef> {
-        let arrays = repeat_n(arr, size).collect::<Vec<_>>();
-        let ret = match !arrays.is_empty() {
-            true => arrow::compute::concat(arrays.as_slice())?,
-            false => arr.slice(0, 0),
-        };
-        Ok(ret)
+        if size == 0 {
+            return Ok(arr.slice(0, 0));
+        }
+        if arr.len() == 1 {
+            // We will typically be called with a 1-element array; in that case,
+            // use `take` rather than `concat`. Using `concat` is very expensive
+            // if the inner element type is `StringViewArray` because it results
+            // in making `size` copies of the StringViewArray, each with their
+            // own data buffers. All of these data buffers will be preserved by
+            // `concat`, making subsequent access to the resulting ListArray
+            // very expensive.
+            //
+            // Using `take` preserves the source array's buffer count, avoiding
+            // this problem.
+            let indices = UInt32Array::from(vec![0; size]);
+            Ok(arrow::compute::take(arr, &indices, None)?)
+        } else {
+            let arrays = repeat_n(arr, size).collect::<Vec<_>>();
+            Ok(arrow::compute::concat(arrays.as_slice())?)
+        }
     }
 
     /// Retrieve ScalarValue for each row in `array`

From e8b0d6cf98922c0648627bd5ab7de3407d06e74c Mon Sep 17 00:00:00 2001
From: Neil Conway <neil.conway@gmail.com>
Date: Fri, 20 Feb 2026 20:17:45 -0500
Subject: [PATCH 3/9] Fix clippy

---
 datafusion/common/benches/scalar_to_array.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/datafusion/common/benches/scalar_to_array.rs b/datafusion/common/benches/scalar_to_array.rs
index 8ef836150f1f..90a152e515fe 100644
--- a/datafusion/common/benches/scalar_to_array.rs
+++ b/datafusion/common/benches/scalar_to_array.rs
@@ -28,7 +28,7 @@ use std::sync::Arc;
 /// Build a `ScalarValue::List` of `num_elements` Utf8View strings whose
 /// inner StringViewArray has `num_buffers` data buffers.
 fn make_list_scalar(num_elements: usize, num_buffers: usize) -> ScalarValue {
-    let elements_per_buffer = (num_elements + num_buffers - 1) / num_buffers;
+    let elements_per_buffer = num_elements.div_ceil(num_buffers);
 
     let mut small_arrays: Vec<ArrayRef> = Vec::new();
     let mut remaining = num_elements;

From 4d6c672abe48996b1b61d732fdd6f5841cb5c259 Mon Sep 17 00:00:00 2001
From: Neil Conway <neil.conway@gmail.com>
Date: Fri, 20 Feb 2026 21:06:38 -0500
Subject: [PATCH 4/9] Generalize to arrays of any length

---
 datafusion/common/src/scalar/mod.rs | 21 ++++-----------------
 1 file changed, 4 insertions(+), 17 deletions(-)

diff --git a/datafusion/common/src/scalar/mod.rs b/datafusion/common/src/scalar/mod.rs
index 32684056ae2e..d0f2b21d93d4 100644
--- a/datafusion/common/src/scalar/mod.rs
+++ b/datafusion/common/src/scalar/mod.rs
@@ -3438,23 +3438,10 @@ impl ScalarValue {
         if size == 0 {
             return Ok(arr.slice(0, 0));
         }
-        if arr.len() == 1 {
-            // We will typically be called with a 1-element array; in that case,
-            // use `take` rather than `concat`. Using `concat` is very expensive
-            // if the inner element type is `StringViewArray` because it results
-            // in making `size` copies of the StringViewArray, each with their
-            // own data buffers. All of these data buffers will be preserved by
-            // `concat`, making subsequent access to the resulting ListArray
-            // very expensive.
-            //
-            // Using `take` preserves the source array's buffer count, avoiding
-            // this problem.
-            let indices = UInt32Array::from(vec![0; size]);
-            Ok(arrow::compute::take(arr, &indices, None)?)
-        } else {
-            let arrays = repeat_n(arr, size).collect::<Vec<_>>();
-            Ok(arrow::compute::concat(arrays.as_slice())?)
-        }
+
+        let n = arr.len() as u32;
+        let indices = UInt32Array::from_iter_values((0..size).flat_map(|_| 0..n));
+        Ok(arrow::compute::take(arr, &indices, None)?)
     }
 
     /// Retrieve ScalarValue for each row in `array`

From 54ba21f51e61dc5e061921030778284b349f5bc6 Mon Sep 17 00:00:00 2001
From: Neil Conway <neil.conway@gmail.com>
Date: Sat, 21 Feb 2026 10:37:36 -0500
Subject: [PATCH 5/9] Add test cases

---
 datafusion/common/src/scalar/mod.rs | 88 +++++++++++++++++++++++++++++
 1 file changed, 88 insertions(+)

diff --git a/datafusion/common/src/scalar/mod.rs b/datafusion/common/src/scalar/mod.rs
index d0f2b21d93d4..c8963de23bc1 100644
--- a/datafusion/common/src/scalar/mod.rs
+++ b/datafusion/common/src/scalar/mod.rs
@@ -5533,6 +5533,94 @@ mod tests {
         assert_eq!(empty_array.len(), 0);
     }
 
+    #[test]
+    fn test_to_array_of_size_list_size_one() {
+        // size=1 takes the fast path (Arc::clone), verify it returns
+        // the same data
+        let arr = ListArray::from_iter_primitive::<Int32Type, _, _>(vec![Some(vec![
+            Some(10),
+            Some(20),
+        ])]);
+        let sv = ScalarValue::List(Arc::new(arr.clone()));
+        let result = sv.to_array_of_size(1).unwrap();
+        assert_eq!(result.len(), 1);
+        assert_eq!(&arr, result.as_list::<i32>());
+    }
+
+    #[test]
+    fn test_to_array_of_size_list_empty_inner() {
+        // A list scalar containing an empty list: [[]]
+        let arr = ListArray::from_iter_primitive::<Int32Type, _, _>(vec![Some(Vec::<
+            Option<i32>,
+        >::new(
+        ))]);
+        let sv = ScalarValue::List(Arc::new(arr));
+        let result = sv.to_array_of_size(3).unwrap();
+        let result_list = result.as_list::<i32>();
+        assert_eq!(result_list.len(), 3);
+        for i in 0..3 {
+            assert_eq!(result_list.value(i).len(), 0);
+        }
+    }
+
+    #[test]
+    fn test_to_array_of_size_large_list() {
+        // LargeList (i64 offsets) with size=3
+        let mut builder = LargeListBuilder::new(Int32Builder::new());
+        builder.values().append_value(100);
+        builder.values().append_value(200);
+        builder.append(true);
+        let arr = builder.finish();
+        let sv = ScalarValue::LargeList(Arc::new(arr));
+
+        let result = sv.to_array_of_size(3).unwrap();
+        let result_list = result.as_list::<i64>();
+        assert_eq!(result_list.len(), 3);
+        for i in 0..3 {
+            let inner = result_list.value(i);
+            let inner_vals = inner.as_primitive::<Int32Type>();
+            assert_eq!(inner_vals.values().as_ref(), &[100, 200]);
+        }
+    }
+
+    #[test]
+    fn test_list_to_array_of_size_multi_row() {
+        // Call list_to_array_of_size directly with arr.len() > 1 to
+        // ensure that works, although this is never done today.
+        let arr = Int32Array::from(vec![Some(10), None, Some(30)]);
+        let result = ScalarValue::list_to_array_of_size(&arr, 3).unwrap();
+        // 3 rows * 3 elements = 9 total
+        assert_eq!(result.len(), 9);
+        let result = result.as_primitive::<Int32Type>();
+        assert_eq!(
+            result.iter().collect::<Vec<_>>(),
+            vec![
+                Some(10),
+                None,
+                Some(30),
+                Some(10),
+                None,
+                Some(30),
+                Some(10),
+                None,
+                Some(30),
+            ]
+        );
+    }
+
+    #[test]
+    fn test_to_array_of_size_null_list() {
+        // A null List scalar replicated to size 3
+        let sv = ScalarValue::try_from(&DataType::List(Arc::new(Field::new_list_field(
+            DataType::Int32,
+            true,
+        ))))
+        .unwrap();
+        let result = sv.to_array_of_size(3).unwrap();
+        assert_eq!(result.len(), 3);
+        assert_eq!(result.null_count(), 3);
+    }
+
     /// See https://github.com/apache/datafusion/issues/18870
     #[test]
     fn test_to_array_of_size_for_none_fsb() {

From 5bd425ec8a959693b3feeaac90c4b3414eb90dc2 Mon Sep 17 00:00:00 2001
From: Neil Conway <neil.conway@gmail.com>
Date: Sat, 21 Feb 2026 10:50:52 -0500
Subject: [PATCH 6/9] Code cleanup for tests

---
 datafusion/common/src/scalar/mod.rs | 62 +++++++++--------------------
 1 file changed, 19 insertions(+), 43 deletions(-)

diff --git a/datafusion/common/src/scalar/mod.rs b/datafusion/common/src/scalar/mod.rs
index c8963de23bc1..90e003ac714f 100644
--- a/datafusion/common/src/scalar/mod.rs
+++ b/datafusion/common/src/scalar/mod.rs
@@ -5535,25 +5535,19 @@ mod tests {
 
     #[test]
     fn test_to_array_of_size_list_size_one() {
-        // size=1 takes the fast path (Arc::clone), verify it returns
-        // the same data
+        // size=1 takes the fast path (Arc::clone)
         let arr = ListArray::from_iter_primitive::<Int32Type, _, _>(vec![Some(vec![
-            Some(10),
-            Some(20),
+            Some(10), Some(20),
         ])]);
         let sv = ScalarValue::List(Arc::new(arr.clone()));
         let result = sv.to_array_of_size(1).unwrap();
-        assert_eq!(result.len(), 1);
-        assert_eq!(&arr, result.as_list::<i32>());
+        assert_eq!(result.as_list::<i32>(), &arr);
     }
 
     #[test]
     fn test_to_array_of_size_list_empty_inner() {
         // A list scalar containing an empty list: [[]]
-        let arr = ListArray::from_iter_primitive::<Int32Type, _, _>(vec![Some(Vec::<
-            Option<i32>,
-        >::new(
-        ))]);
+        let arr = ListArray::from_iter_primitive::<Int32Type, _, _>(vec![Some(vec![])]);
         let sv = ScalarValue::List(Arc::new(arr));
         let result = sv.to_array_of_size(3).unwrap();
         let result_list = result.as_list::<i32>();
@@ -5565,57 +5559,39 @@ mod tests {
 
     #[test]
     fn test_to_array_of_size_large_list() {
-        // LargeList (i64 offsets) with size=3
-        let mut builder = LargeListBuilder::new(Int32Builder::new());
-        builder.values().append_value(100);
-        builder.values().append_value(200);
-        builder.append(true);
-        let arr = builder.finish();
+        let arr = LargeListArray::from_iter_primitive::<Int32Type, _, _>(vec![Some(
+            vec![Some(100), Some(200)],
+        )]);
         let sv = ScalarValue::LargeList(Arc::new(arr));
-
         let result = sv.to_array_of_size(3).unwrap();
-        let result_list = result.as_list::<i64>();
-        assert_eq!(result_list.len(), 3);
-        for i in 0..3 {
-            let inner = result_list.value(i);
-            let inner_vals = inner.as_primitive::<Int32Type>();
-            assert_eq!(inner_vals.values().as_ref(), &[100, 200]);
-        }
+        let expected = LargeListArray::from_iter_primitive::<Int32Type, _, _>(vec![
+            Some(vec![Some(100), Some(200)]),
+            Some(vec![Some(100), Some(200)]),
+            Some(vec![Some(100), Some(200)]),
+        ]);
+        assert_eq!(result.as_list::<i64>(), &expected);
     }
 
     #[test]
     fn test_list_to_array_of_size_multi_row() {
-        // Call list_to_array_of_size directly with arr.len() > 1 to
-        // ensure that works, although this is never done today.
+        // Call list_to_array_of_size directly with arr.len() > 1
         let arr = Int32Array::from(vec![Some(10), None, Some(30)]);
         let result = ScalarValue::list_to_array_of_size(&arr, 3).unwrap();
-        // 3 rows * 3 elements = 9 total
-        assert_eq!(result.len(), 9);
         let result = result.as_primitive::<Int32Type>();
         assert_eq!(
             result.iter().collect::<Vec<_>>(),
             vec![
-                Some(10),
-                None,
-                Some(30),
-                Some(10),
-                None,
-                Some(30),
-                Some(10),
-                None,
-                Some(30),
+                Some(10), None, Some(30),
+                Some(10), None, Some(30),
+                Some(10), None, Some(30),
             ]
         );
     }
 
     #[test]
     fn test_to_array_of_size_null_list() {
-        // A null List scalar replicated to size 3
-        let sv = ScalarValue::try_from(&DataType::List(Arc::new(Field::new_list_field(
-            DataType::Int32,
-            true,
-        ))))
-        .unwrap();
+        let dt = DataType::List(Arc::new(Field::new_list_field(DataType::Int32, true)));
+        let sv = ScalarValue::try_from(&dt).unwrap();
         let result = sv.to_array_of_size(3).unwrap();
         assert_eq!(result.len(), 3);
         assert_eq!(result.null_count(), 3);

From 8828b54f6ad6b8806b96154c72720de336169781 Mon Sep 17 00:00:00 2001
From: Neil Conway <neil.conway@gmail.com>
Date: Sat, 21 Feb 2026 10:55:23 -0500
Subject: [PATCH 7/9] cargo fmt

---
 datafusion/common/src/scalar/mod.rs | 23 ++++++++++++++++-------
 1 file changed, 16 insertions(+), 7 deletions(-)

diff --git a/datafusion/common/src/scalar/mod.rs b/datafusion/common/src/scalar/mod.rs
index 90e003ac714f..23aba2255f63 100644
--- a/datafusion/common/src/scalar/mod.rs
+++ b/datafusion/common/src/scalar/mod.rs
@@ -5537,7 +5537,8 @@ mod tests {
     fn test_to_array_of_size_list_size_one() {
         // size=1 takes the fast path (Arc::clone)
         let arr = ListArray::from_iter_primitive::<Int32Type, _, _>(vec![Some(vec![
-            Some(10), Some(20),
+            Some(10),
+            Some(20),
         ])]);
         let sv = ScalarValue::List(Arc::new(arr.clone()));
         let result = sv.to_array_of_size(1).unwrap();
@@ -5559,9 +5560,11 @@ mod tests {
 
     #[test]
     fn test_to_array_of_size_large_list() {
-        let arr = LargeListArray::from_iter_primitive::<Int32Type, _, _>(vec![Some(
-            vec![Some(100), Some(200)],
-        )]);
+        let arr =
+            LargeListArray::from_iter_primitive::<Int32Type, _, _>(vec![Some(vec![
+                Some(100),
+                Some(200),
+            ])]);
         let sv = ScalarValue::LargeList(Arc::new(arr));
         let result = sv.to_array_of_size(3).unwrap();
         let expected = LargeListArray::from_iter_primitive::<Int32Type, _, _>(vec![
@@ -5581,9 +5584,15 @@ mod tests {
         assert_eq!(
             result.iter().collect::<Vec<_>>(),
             vec![
-                Some(10), None, Some(30),
-                Some(10), None, Some(30),
-                Some(10), None, Some(30),
+                Some(10),
+                None,
+                Some(30),
+                Some(10),
+                None,
+                Some(30),
+                Some(10),
+                None,
+                Some(30),
             ]
         );
     }

From d782c38dafc6d9f71c1edf73a9f9dbf10a687919 Mon Sep 17 00:00:00 2001
From: Neil Conway <neil.conway@gmail.com>
Date: Sat, 21 Feb 2026 21:50:50 -0500
Subject: [PATCH 8/9] Add comment with example behavior

---
 datafusion/common/src/scalar/mod.rs | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/datafusion/common/src/scalar/mod.rs b/datafusion/common/src/scalar/mod.rs
index 23aba2255f63..f90e38fc57ff 100644
--- a/datafusion/common/src/scalar/mod.rs
+++ b/datafusion/common/src/scalar/mod.rs
@@ -3434,11 +3434,19 @@ impl ScalarValue {
         }
     }
 
+    /// Repeats the rows of `arr` `size` times, producing an array with
+    /// `arr.len() * size` total rows.
     fn list_to_array_of_size(arr: &dyn Array, size: usize) -> Result<ArrayRef> {
         if size == 0 {
             return Ok(arr.slice(0, 0));
         }
 
+        // Examples: given `arr = [[A, B, C]]` and `size = 3`, `indices = [0, 0, 0]` and
+        // the result is `[[A, B, C], [A, B, C], [A, B, C]]`.
+        //
+        // Given `arr = [[A, B], [C]]` and `size = 2`, `indices = [0, 1, 0, 1]` and the
+        // result is `[[A, B], [C], [A, B], [C]]`. (But in practice, we are always called
+        // with `arr.len() == 1`.)
         let n = arr.len() as u32;
         let indices = UInt32Array::from_iter_values((0..size).flat_map(|_| 0..n));
         Ok(arrow::compute::take(arr, &indices, None)?)

From b8549b40148e0e79ee7cf93ef605f8ec0b03c6b2 Mon Sep 17 00:00:00 2001
From: Neil Conway <neil.conway@gmail.com>
Date: Sun, 22 Feb 2026 11:15:58 -0500
Subject: [PATCH 9/9] Update comment in NLJ

---
 datafusion/physical-plan/src/joins/nested_loop_join.rs | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/datafusion/physical-plan/src/joins/nested_loop_join.rs b/datafusion/physical-plan/src/joins/nested_loop_join.rs
index 5b2cebb36043..33fec9e18180 100644
--- a/datafusion/physical-plan/src/joins/nested_loop_join.rs
+++ b/datafusion/physical-plan/src/joins/nested_loop_join.rs
@@ -2011,9 +2011,10 @@ fn build_row_join_batch(
             // Broadcast the single build-side row to match the filtered
             // probe-side batch length
             let original_left_array = build_side_batch.column(column_index.index);
-            // Avoid using `ScalarValue::to_array_of_size()` for `List(Utf8View)` to avoid
-            // deep copies for buffers inside `Utf8View` array. See below for details.
-            // https://github.com/apache/datafusion/issues/18159
+
+            // Use `arrow::compute::take` directly for `List(Utf8View)` rather
+            // than going through `ScalarValue::to_array_of_size()`, which
+            // avoids some intermediate allocations.
             //
             // In other cases, `to_array_of_size()` is faster.
             match original_left_array.data_type() {
