Scala - Array with Examples

Scala provides multiple ways to instantiate arrays depending on your use case. The most common approach uses the `Array` companion object's `apply` method.

Key Insights

  • Scala arrays are mutable, fixed-size collections that map directly to Java arrays, offering O(1) element access and maximum performance for numerical computations
  • Array operations in Scala provide both imperative mutation methods and functional transformation methods that return new arrays, giving you flexibility in programming style
  • Multi-dimensional arrays, array buffers for dynamic sizing, and seamless Java interoperability make Scala arrays practical for real-world applications from data processing to performance-critical systems

Creating Arrays

Scala provides multiple ways to instantiate arrays depending on your use case. The most common approach uses the Array companion object’s apply method.

// Direct initialization with elements
val numbers = Array(1, 2, 3, 4, 5)
val strings = Array("Scala", "Java", "Kotlin")

// Creating arrays with specific size and default values
val zeros = new Array[Int](10)  // Array of 10 zeros
val emptyStrings = new Array[String](5)  // Array of 5 nulls

// Using Array.fill for custom initialization
val ones = Array.fill(5)(1)  // Array(1, 1, 1, 1, 1)
val random = Array.fill(5)(scala.util.Random.nextInt(100))

// Using Array.tabulate for index-based initialization
val squares = Array.tabulate(5)(n => n * n)  // Array(0, 1, 4, 9, 16)

// Range conversion to array
val range = (1 to 10).toArray

The type parameter is inferred from the elements, but you can specify it explicitly when needed:

val mixed: Array[Any] = Array(1, "two", 3.0, true)
val numbers: Array[Number] = Array(1, 2.5, 3L)

Accessing and Modifying Elements

Arrays use zero-based indexing with parentheses for both reading and writing. This syntax differs from most collections, which use parentheses only for reading.

val arr = Array(10, 20, 30, 40, 50)

// Reading elements
val first = arr(0)        // 10
val last = arr(arr.length - 1)  // 50

// Modifying elements (arrays are mutable)
arr(0) = 100
arr(2) = arr(1) + arr(3)  // 120

// Safe access with bounds checking
val safeGet = arr.lift(10)  // Option[Int] = None
val withDefault = arr.applyOrElse(10, (_: Int) => -1)  // -1

// Pattern matching on array size
arr match {
  case Array(x) => println(s"Single element: $x")
  case Array(x, y) => println(s"Two elements: $x, $y")
  case Array(x, y, _*) => println(s"At least two: $x, $y")
  case _ => println("Empty array")
}

Array Operations and Transformations

Scala arrays support both imperative operations that modify in place and functional operations that return new arrays.

val numbers = Array(1, 2, 3, 4, 5)

// Functional transformations (return new arrays)
val doubled = numbers.map(_ * 2)  // Array(2, 4, 6, 8, 10)
val evens = numbers.filter(_ % 2 == 0)  // Array(2, 4)
val sum = numbers.reduce(_ + _)  // 15

// Chaining operations
val result = numbers
  .filter(_ > 2)
  .map(_ * 10)
  .sorted  // Array(30, 40, 50)

// FlatMap for nested structures
val nested = Array(Array(1, 2), Array(3, 4))
val flattened = nested.flatMap(identity)  // Array(1, 2, 3, 4)

// Zip and unzip
val letters = Array("a", "b", "c")
val zipped = numbers.zip(letters)  // Array((1,a), (2,b), (3,c))

// GroupBy for categorization
val grouped = numbers.groupBy(_ % 2)
// Map(1 -> Array(1, 3, 5), 0 -> Array(2, 4))

For in-place modifications:

val mutable = Array(5, 2, 8, 1, 9)

// Sorting in place
scala.util.Sorting.quickSort(mutable)  // mutable is now sorted

// Transform in place
for (i <- mutable.indices) {
  mutable(i) = mutable(i) * 2
}

Multi-Dimensional Arrays

Multi-dimensional arrays are arrays of arrays, useful for matrix operations and grid-based data structures.

// Creating 2D arrays
val matrix = Array.ofDim[Int](3, 4)  // 3x4 matrix of zeros

// Initializing with values
val grid = Array(
  Array(1, 2, 3),
  Array(4, 5, 6),
  Array(7, 8, 9)
)

// Accessing elements
val element = grid(1)(2)  // 6 (row 1, column 2)
grid(0)(0) = 100

// Iterating over 2D array
for {
  i <- grid.indices
  j <- grid(i).indices
} {
  println(s"grid($i)($j) = ${grid(i)(j)}")
}

// Matrix operations
def transposeMatrix(matrix: Array[Array[Int]]): Array[Array[Int]] = {
  val rows = matrix.length
  val cols = matrix(0).length
  Array.tabulate(cols, rows)((i, j) => matrix(j)(i))
}

val transposed = transposeMatrix(grid)

// 3D arrays
val cube = Array.ofDim[Double](10, 10, 10)
cube(5)(5)(5) = 42.0

ArrayBuffer for Dynamic Arrays

When you need a resizable array, use ArrayBuffer from the mutable collections library.

import scala.collection.mutable.ArrayBuffer

val buffer = ArrayBuffer[Int]()

// Adding elements
buffer += 1           // Append single element
buffer += (2, 3, 4)   // Append multiple elements
buffer ++= Array(5, 6, 7)  // Append collection
buffer.prepend(0)     // Add to beginning
buffer.insert(4, 99)  // Insert at index

// Removing elements
buffer -= 99          // Remove by value
buffer.remove(0)      // Remove by index
buffer.trimEnd(2)     // Remove last 2 elements

// Convert to immutable array
val finalArray = buffer.toArray

// Efficient batch operations
val large = ArrayBuffer.fill(10000)(0)
large.transform(_ + 1)  // In-place transformation

Performance Considerations and Best Practices

Arrays provide the best performance for indexed access and numerical computations, but understanding their characteristics helps you choose the right collection.

// Benchmark: Array vs List for indexed access
def arrayAccess(arr: Array[Int], n: Int): Long = {
  val start = System.nanoTime()
  var sum = 0
  for (i <- 0 until n) {
    sum += arr(i % arr.length)
  }
  System.nanoTime() - start
}

val testArray = Array.fill(1000)(1)
val iterations = 1000000
val arrayTime = arrayAccess(testArray, iterations)

// Arrays excel at:
// 1. Random access O(1)
// 2. Memory efficiency (contiguous storage)
// 3. Primitive type storage without boxing

// Use ArrayBuffer when:
val dynamic = ArrayBuffer[String]()
// - Size is unknown upfront
// - Frequent additions/removals
// - Eventually convert to Array

// Avoid arrays for:
// - Functional programming patterns requiring immutability
// - Frequent prepend operations (use List)
// - Need for structural sharing (use Vector)

Java Interoperability

Scala arrays compile to Java arrays, enabling seamless integration with Java libraries.

// Scala Array is Java array
val scalaArray: Array[String] = Array("a", "b", "c")
val javaArray: Array[String] = scalaArray  // No conversion needed

// Calling Java methods expecting arrays
import java.util.Arrays
val sorted = Arrays.copyOf(scalaArray, scalaArray.length)
Arrays.sort(sorted)

// Converting Java collections to Scala arrays
import scala.jdk.CollectionConverters._
val javaList = java.util.Arrays.asList("x", "y", "z")
val scalaArr = javaList.asScala.toArray

// Varargs compatibility
def javaVarargs(args: String*): Unit = {
  println(args.mkString(", "))
}
javaVarargs(scalaArray: _*)  // Expand array to varargs

// Working with primitive arrays
val intArray: Array[Int] = Array(1, 2, 3)
// Compiles to Java's int[], not Integer[]
// No boxing overhead for primitives

Advanced Array Patterns

Leverage Scala’s advanced features for sophisticated array manipulation.

// Sliding windows
val data = Array(1, 2, 3, 4, 5)
val windows = data.sliding(3).toArray
// Array(Array(1,2,3), Array(2,3,4), Array(3,4,5))

// Parallel processing for large arrays
val large = Array.fill(1000000)(scala.util.Random.nextInt(100))
val parallelResult = large.par.map(_ * 2).toArray

// Custom ordering
case class Person(name: String, age: Int)
val people = Array(
  Person("Alice", 30),
  Person("Bob", 25),
  Person("Charlie", 35)
)
val byAge = people.sortBy(_.age)
val byName = people.sortWith(_.name < _.name)

// Binary search (requires sorted array)
val sorted = Array(1, 3, 5, 7, 9, 11, 13)
val index = sorted.search(7)  // Found(3)

// Deep equality for nested arrays
val arr1 = Array(Array(1, 2), Array(3, 4))
val arr2 = Array(Array(1, 2), Array(3, 4))
val equal = arr1.deep == arr2.deep  // true

Arrays remain fundamental in Scala for performance-critical code, numerical computing, and Java interoperability. Choose arrays when you need maximum speed and fixed-size storage, ArrayBuffer for dynamic sizing, and consider immutable alternatives like Vector for functional programming patterns.

Liked this? There's more.

Every week: one practical technique, explained simply, with code you can use immediately.