Scala Program to Implement Merge Sort

Scala Program to Implement Merge Sort

Merge Sort is one of those algorithms that many programmers hear about early in their learning journey, but it often feels more advanced than simple methods like Bubble Sort or Selection Sort. Even so, it is worth understanding because it teaches an important way of solving problems by breaking them into smaller parts. It follows a strategy known as “divide and conquer,” where a large problem becomes easier once it is split into tiny steps. This idea shows up everywhere in programming, so studying Merge Sort helps beginners build strong problem-solving habits.

Pluralsight Logo
Accelerate your tech career
with hands-on learning.
Whether you're a tech newbie or a total pro,
get the skills and confidence to land your next move.
Start 10-Day Free Trial

What makes Merge Sort special is its speed and reliability. Unlike many simple sorting algorithms, it handles large datasets very well and keeps its performance steady even as the data grows. It is widely used in real systems because it offers a predictable running time and works beautifully when the data needs to remain stable or when parallel processing is involved. For Scala learners, understanding Merge Sort opens the door to writing efficient programs and appreciating how recursion can make certain problems easier to solve.

Program 1: Simple Recursive Merge Sort

This program presents the basic recursive version of Merge Sort that most programmers learn first. It shows how the array is split into two halves and how each half is sorted before being combined back into one ordered list.

object MergeSortBasic {

  def mergeSort(arr: List[Int]): List[Int] = {

    if (arr.length <= 1) arr
    else {

      val middle = arr.length / 2
      val left = mergeSort(arr.slice(0, middle))
      val right = mergeSort(arr.slice(middle, arr.length))

      merge(left, right)

    }

  }

  def merge(left: List[Int], right: List[Int]): List[Int] = {

    (left, right) match {

      case (Nil, _) => right
      case (_, Nil) => left
      case (lh :: lt, rh :: rt) =>
        if (lh < rh) lh :: merge(lt, right)
        else rh :: merge(left, rt)

    }

  }

  def main(args: Array[String]): Unit = {

    val data = List(9, 3, 7, 2, 8, 5)
    println("Sorted List: " + mergeSort(data))

  }

}

This version focuses on clarity rather than speed. The array is split until only single values remain. Then the merge function puts everything back in order. Beginners often find this version helpful because it clearly shows how recursion breaks big tasks into smaller ones. It also demonstrates how pattern matching in Scala can make merging easy to follow.

Program 2: Merge Sort Using Arrays

Here we use arrays instead of lists to help beginners understand how Merge Sort works on fixed-size structures. Arrays behave differently from lists, so this version highlights indexing and mutation in Scala.

object MergeSortArray {

  def mergeSort(arr: Array[Int]): Array[Int] = {

    if (arr.length <= 1) return arr

    val middle = arr.length / 2
    val left = mergeSort(arr.slice(0, middle))
    val right = mergeSort(arr.slice(middle, arr.length))

    merge(left, right)

  }

  def merge(left: Array[Int], right: Array[Int]): Array[Int] = {

    var i = 0
    var j = 0
    val result = Array.ofDim[Int](left.length + right.length)
    var k = 0

    while (i < left.length && j < right.length) {

      if (left(i) < right(j)) {
        result(k) = left(i)
        i += 1
      } else {
        result(k) = right(j)
        j += 1
      }

      k += 1

    }

    while (i < left.length) {
      result(k) = left(i)
      i += 1
      k += 1
    }

    while (j < right.length) {
      result(k) = right(j)
      j += 1
      k += 1
    }

    result

  }

  def main(args: Array[String]): Unit = {

    val data = Array(4, 1, 6, 3, 9, 2)
    println("Sorted Array: " + mergeSort(data).mkString(", "))

  }

}

This version uses loops to merge instead of recursion. It gives beginners a clear look at how indexes move and how values are copied into a new array. This approach is useful when you want tight control over memory and speed, which can be important in real-day applications.

Program 3: Tail-Recursive Merge Sort

Here we introduce a tail-recursive version. Scala optimizes tail recursion well, so this version shows how performance and readability can remain balanced.

object TailRecursiveMergeSort {

  def mergeSort(arr: List[Int]): List[Int] = {

    if (arr.length <= 1) arr
    else {

      val mid = arr.length / 2
      val left = mergeSort(arr.take(mid))
      val right = mergeSort(arr.drop(mid))

      merge(left, right)

    }

  }

  @scala.annotation.tailrec
  def merge(left: List[Int], right: List[Int], acc: List[Int] = Nil): List[Int] = {

    (left, right) match {

      case (Nil, _) => acc.reverse ::: right
      case (_, Nil) => acc.reverse ::: left
      case (lh :: lt, rh :: rt) =>
        if (lh < rh) merge(lt, right, lh :: acc)
        else merge(left, rt, rh :: acc)

    }

  }

  def main(args: Array[String]): Unit = {

    val data = List(10, 2, 8, 4, 7)
    println("Sorted List: " + mergeSort(data))

  }

}

Tail recursion can feel advanced at first, but the idea is simple: the function ensures that the last step is the recursive call, helping Scala optimize the process. This results in fewer stack calls. Beginners can use this version when they want a more memory-friendly approach that still keeps the logic readable.

Program 4: Merge Sort Using Mutable Buffers

This version uses scala.collection.mutable.ArrayBuffer to help learners explore dynamic arrays. It offers a flexible structure while still maintaining the logic of Merge Sort.

import scala.collection.mutable.ArrayBuffer

object MergeSortBuffer {

  def mergeSort(arr: ArrayBuffer[Int]): ArrayBuffer[Int] = {

    if (arr.length <= 1) return arr

    val mid = arr.length / 2
    val left = mergeSort(arr.slice(0, mid))
    val right = mergeSort(arr.slice(mid, arr.length))

    merge(left, right)

  }

  def merge(left: ArrayBuffer[Int], right: ArrayBuffer[Int]): ArrayBuffer[Int] = {

    val result = ArrayBuffer[Int]()
    var i = 0
    var j = 0

    while (i < left.length && j < right.length) {

      if (left(i) < right(j)) {
        result += left(i)
        i += 1
      } else {
        result += right(j)
        j += 1
      }

    }

    while (i < left.length) {
      result += left(i)
      i += 1
    }

    while (j < right.length) {
      result += right(j)
      j += 1
    }

    result

  }

  def main(args: Array[String]): Unit = {

    val data = ArrayBuffer(12, 5, 3, 9, 15)

    println("Sorted Buffer: " + mergeSort(data))

  }

}

Many Scala beginners like using mutable structures because they behave much like arrays in other languages. This example helps them understand Merge Sort while learning how to work with dynamic collections. It is also useful for situations where data changes often.

Program 5: Merge Sort for Strings

This version sorts strings instead of numbers. It shows how Merge Sort works for any data that Scala can compare.

object MergeSortStrings {

  def mergeSort(arr: List[String]): List[String] = {

    if (arr.length <= 1) arr
    else {

      val mid = arr.length / 2
      val left = mergeSort(arr.take(mid))
      val right = mergeSort(arr.drop(mid))
      merge(left, right)

    }

  }

  def merge(left: List[String], right: List[String]): List[String] = {

    (left, right) match {

      case (Nil, _) => right
      case (_, Nil) => left
      case (lh :: lt, rh :: rt) =>
        if (lh.compareTo(rh) < 0) lh :: merge(lt, right)
        else rh :: merge(left, rt)

    }

  }

  def main(args: Array[String]): Unit = {

    val data = List("mango", "apple", "banana", "peach")

    println("Sorted Words: " + mergeSort(data))

  }

}

This version shows how Merge Sort easily adapts to different data types. As long as the values can be compared, the algorithm works the same way. Beginners learn that sorting is not limited to numbers, which expands their understanding of Scala’s flexibility.

Frequently Asked Questions (FAQ)

This section answers common questions that learners often ask when studying Merge Sort in Scala. The goal is to offer short and simple explanations that make the ideas easier to understand.

Q1: Why is Merge Sort known for good performance?
Merge Sort maintains consistent performance even with large datasets because it divides the data into smaller parts before sorting. This balanced structure helps it avoid slowdowns.

Q2: Is Merge Sort better than Quick Sort?
Merge Sort is more predictable, while Quick Sort can be faster in some cases. The best choice depends on the data and the situation.

Q3: Can Merge Sort work on strings in Scala?
Yes, as long as the values can be compared, Merge Sort will arrange them correctly. This makes it suitable for sorting words, names, or any text data.

Q4: Does Merge Sort always use recursion?
Most versions do, but it can also be written using loops and buffers. Scala supports both styles, giving programmers flexibility.

Q5: When should I use Merge Sort in real projects?
It is a good choice for large datasets or when you need stable sorting. Its behavior stays consistent even as the data grows.

Conclusion

Merge Sort is a powerful and dependable sorting technique that teaches beginners how to think in smaller steps. It brings together recursion, splitting, merging, and careful problem-solving, making it an excellent algorithm for building strong programming habits. Scala’s support for both immutable and mutable structures also makes it easy to explore different versions of the algorithm.

With the programs above, you now have several ways to implement Merge Sort in Scala. Try experimenting with different data types, sizes, and structures so you can see how each version behaves. The more you practice, the more confident you will become in both Scala and algorithm design.

Scroll to Top