Logo
Published on

Complete Guide to Sorting Algorithms - Bubble, Selection, Insertion, Merge, Quick, Counting & Bucket Sort

Complete Guide to Sorting Algorithms

Sorting algorithms are fundamental to computer science and essential for technical interviews. This guide covers all major sorting techniques with concise explanations, Go implementations, and practical analysis.

Overview of Sorting Algorithms

What is Sorting? Arranging elements in a specific order (ascending or descending).

Key Considerations:

  • Time Complexity: How fast is the algorithm?
  • Space Complexity: How much extra memory is needed?
  • Stability: Does it preserve relative order of equal elements?
  • In-place: Does it sort without extra space?

1. Bubble Sort

๏ฟฝ Definition

Bubble Sort repeatedly compares adjacent elements and swaps them if they're in wrong order. Larger elements "bubble up" to the end.

Algorithm Steps

  1. Compare adjacent elements from start
  2. Swap if left > right (for ascending order)
  3. Continue until end of array
  4. Repeat for n-1 passes

Implementation

package main

import "fmt"

func BubbleSort(arr []int) {
    n := len(arr)
    for i := 0; i < n-1; i++ {
        // Flag to optimize - if no swaps, array is sorted
        swapped := false
        for j := 0; j < n-i-1; j++ {
            if arr[j] > arr[j+1] {
                arr[j], arr[j+1] = arr[j+1], arr[j] // Swap
                swapped = true
            }
        }
        // If no swaps occurred, array is sorted
        if !swapped {
            break
        }
    }
}

func main() {
    arr := []int{64, 34, 25, 12, 22, 11, 90}
    fmt.Println("Original:", arr)
    BubbleSort(arr)
    fmt.Println("Sorted:", arr)
}

Dry Run Example

Array: [5, 2, 8, 1]

Pass 1: [5,2,8,1] โ†’ [2,5,8,1] โ†’ [2,5,8,1] โ†’ [2,5,1,8]
Pass 2: [2,5,1,8] โ†’ [2,5,1,8] โ†’ [2,1,5,8]
Pass 3: [2,1,5,8] โ†’ [1,2,5,8]
Result: [1,2,5,8]

Complexity

  • Time: Best O(n), Average/Worst O(nยฒ)
  • Space: O(1)
  • Stable: Yes
  • In-place: Yes

When to Use

โœ… Use When: Educational purposes, very small datasets
โŒ Avoid When: Large datasets, performance matters


2. Selection Sort

๐ŸŽฏ Definition

Selection Sort finds the minimum element and places it at the beginning. Repeat for remaining unsorted portion.

Algorithm Steps

  1. Find minimum element in unsorted array
  2. Swap it with first element
  3. Move boundary of unsorted array
  4. Repeat until array is sorted

Implementation

package main

import "fmt"

func SelectionSort(arr []int) {
    n := len(arr)
    for i := 0; i < n-1; i++ {
        // Find minimum element index
        minIdx := i
        for j := i + 1; j < n; j++ {
            if arr[j] < arr[minIdx] {
                minIdx = j
            }
        }
        // Swap minimum element with first element
        arr[i], arr[minIdx] = arr[minIdx], arr[i]
    }
}

func main() {
    arr := []int{64, 25, 12, 22, 11}
    fmt.Println("Original:", arr)
    SelectionSort(arr)
    fmt.Println("Sorted:", arr)
}

Dry Run Example

Array: [5, 2, 8, 1]

Pass 1: Find min(5,2,8,1)=1, swap: [1,2,8,5]
Pass 2: Find min(2,8,5)=2, no swap: [1,2,8,5]
Pass 3: Find min(8,5)=5, swap: [1,2,5,8]
Result: [1,2,5,8]

Complexity

  • Time: O(nยฒ) for all cases
  • Space: O(1)
  • Stable: No
  • In-place: Yes

When to Use

โœ… Use When: Memory is limited, small datasets
โŒ Avoid When: Large datasets, stability required


3. Insertion Sort

๐Ÿ“ Definition

Insertion Sort builds sorted array one element at a time by inserting each element into its correct position.

Algorithm Steps

  1. Start from second element (assume first is sorted)
  2. Compare current element with previous elements
  3. Shift larger elements to right
  4. Insert current element at correct position

Implementation

package main

import "fmt"

func InsertionSort(arr []int) {
    n := len(arr)
    for i := 1; i < n; i++ {
        key := arr[i]
        j := i - 1
        
        // Move elements greater than key to one position ahead
        for j >= 0 && arr[j] > key {
            arr[j+1] = arr[j]
            j--
        }
        arr[j+1] = key
    }
}

func main() {
    arr := []int{5, 2, 4, 6, 1, 3}
    fmt.Println("Original:", arr)
    InsertionSort(arr)
    fmt.Println("Sorted:", arr)
}

Dry Run Example

Array: [5, 2, 8, 1]

i=1: key=2, shift 5: [2,5,8,1]
i=2: key=8, no shift: [2,5,8,1]
i=3: key=1, shift 8,5,2: [1,2,5,8]
Result: [1,2,5,8]

Complexity

  • Time: Best O(n), Average/Worst O(nยฒ)
  • Space: O(1)
  • Stable: Yes
  • In-place: Yes

When to Use

โœ… Use When: Small datasets, nearly sorted data, online algorithm needed
โŒ Avoid When: Large datasets with random order

4. Merge Sort

๏ฟฝ Definition

Merge Sort uses divide-and-conquer: divide array into halves, sort them recursively, then merge sorted halves.

Algorithm Steps

  1. Divide array into two halves
  2. Recursively sort both halves
  3. Merge the sorted halves
  4. Base case: arrays with 1 element are sorted

Implementation

package main

import "fmt"

func MergeSort(arr []int) []int {
    if len(arr) <= 1 {
        return arr
    }
    
    mid := len(arr) / 2
    left := MergeSort(arr[:mid])
    right := MergeSort(arr[mid:])
    
    return merge(left, right)
}

func merge(left, right []int) []int {
    result := make([]int, 0, len(left)+len(right))
    i, j := 0, 0
    
    // Merge while both arrays have elements
    for i < len(left) && j < len(right) {
        if left[i] <= right[j] {
            result = append(result, left[i])
            i++
        } else {
            result = append(result, right[j])
            j++
        }
    }
    
    // Append remaining elements
    result = append(result, left[i:]...)
    result = append(result, right[j:]...)
    
    return result
}

func main() {
    arr := []int{38, 27, 43, 3, 9, 82, 10}
    fmt.Println("Original:", arr)
    sorted := MergeSort(arr)
    fmt.Println("Sorted:", sorted)
}

Dry Run Example

Array: [4, 2, 7, 1]

Divide: [4,2,7,1] โ†’ [4,2] & [7,1]
Further: [4] & [2], [7] & [1]
Merge: [2,4] & [1,7]
Final merge: [1,2,4,7]

Complexity

  • Time: O(n log n) for all cases
  • Space: O(n)
  • Stable: Yes
  • In-place: No

When to Use

โœ… Use When: Large datasets, stability required, guaranteed O(n log n)
โŒ Avoid When: Memory is very limited


5. Quick Sort

โšก Definition

Quick Sort picks a pivot, partitions array around it, then recursively sorts partitions.

Algorithm Steps

  1. Choose a pivot element
  2. Partition: elements < pivot on left, > pivot on right
  3. Recursively sort left and right partitions
  4. Combine results

Implementation

package main

import "fmt"

func QuickSort(arr []int, low, high int) {
    if low < high {
        // Partition and get pivot index
        pi := partition(arr, low, high)
        
        // Recursively sort elements before and after partition
        QuickSort(arr, low, pi-1)
        QuickSort(arr, pi+1, high)
    }
}

func partition(arr []int, low, high int) int {
    pivot := arr[high] // Choose last element as pivot
    i := low - 1       // Index of smaller element
    
    for j := low; j < high; j++ {
        if arr[j] <= pivot {
            i++
            arr[i], arr[j] = arr[j], arr[i]
        }
    }
    arr[i+1], arr[high] = arr[high], arr[i+1]
    return i + 1
}

func main() {
    arr := []int{10, 7, 8, 9, 1, 5}
    fmt.Println("Original:", arr)
    QuickSort(arr, 0, len(arr)-1)
    fmt.Println("Sorted:", arr)
}

Dry Run Example

Array: [4, 2, 7, 1], Pivot: 1

Partition: [1] | [4,2,7] (pivot at index 0)
Right partition: [4,2,7], Pivot: 7
Result: [1] | [2,4] | [7]
Final: [1,2,4,7]

Complexity

  • Time: Best/Average O(n log n), Worst O(nยฒ)
  • Space: O(log n) average, O(n) worst
  • Stable: No
  • In-place: Yes

When to Use

โœ… Use When: Average case performance matters, in-place sorting needed
โŒ Avoid When: Worst-case guarantee required


6. Counting Sort

๏ฟฝ Definition

Counting Sort counts occurrences of each element, then reconstructs sorted array. Works only with integers in known range.

Algorithm Steps

  1. Find maximum element to determine range
  2. Create count array for range [0...max]
  3. Count occurrences of each element
  4. Transform counts to actual positions
  5. Build sorted array using positions

Implementation

package main

import "fmt"

func CountingSort(arr []int) []int {
    if len(arr) == 0 {
        return arr
    }
    
    // Find maximum element
    max := arr[0]
    for _, v := range arr {
        if v > max {
            max = v
        }
    }
    
    // Count occurrences
    count := make([]int, max+1)
    for _, v := range arr {
        count[v]++
    }
    
    // Transform counts to positions
    for i := 1; i <= max; i++ {
        count[i] += count[i-1]
    }
    
    // Build result array
    result := make([]int, len(arr))
    for i := len(arr) - 1; i >= 0; i-- {
        result[count[arr[i]]-1] = arr[i]
        count[arr[i]]--
    }
    
    return result
}

func main() {
    arr := []int{4, 2, 2, 8, 3, 3, 1}
    fmt.Println("Original:", arr)
    sorted := CountingSort(arr)
    fmt.Println("Sorted:", sorted)
}

Dry Run Example

Array: [4, 2, 2, 1], Max: 4

Count: [0,1,2,0,1] (counts for 0,1,2,3,4)
Positions: [0,1,3,3,4]
Build: Place elements using positions
Result: [1,2,2,4]

Complexity

  • Time: O(n + k) where k is range
  • Space: O(k)
  • Stable: Yes
  • In-place: No

When to Use

โœ… Use When: Small range of integers, need stability
โŒ Avoid When: Large range, floating-point numbers


7. Bucket Sort

๐Ÿชฃ Definition

Bucket Sort distributes elements into buckets, sorts each bucket individually, then concatenates results.

Algorithm Steps

  1. Create empty buckets
  2. Distribute elements into buckets based on range
  3. Sort each bucket individually
  4. Concatenate all bucket contents

Implementation

package main

import (
    "fmt"
    "sort"
)

func BucketSort(arr []float64, numBuckets int) []float64 {
    if len(arr) == 0 {
        return arr
    }
    
    // Find min and max values
    min, max := arr[0], arr[0]
    for _, v := range arr {
        if v < min {
            min = v
        }
        if v > max {
            max = v
        }
    }
    
    // Create buckets
    buckets := make([][]float64, numBuckets)
    for i := range buckets {
        buckets[i] = make([]float64, 0)
    }
    
    // Distribute elements into buckets
    bucketRange := (max - min) / float64(numBuckets)
    for _, v := range arr {
        bucketIndex := int((v - min) / bucketRange)
        if bucketIndex >= numBuckets {
            bucketIndex = numBuckets - 1
        }
        buckets[bucketIndex] = append(buckets[bucketIndex], v)
    }
    
    // Sort each bucket and concatenate
    result := make([]float64, 0, len(arr))
    for _, bucket := range buckets {
        sort.Float64s(bucket)
        result = append(result, bucket...)
    }
    
    return result
}

func main() {
    arr := []float64{0.897, 0.565, 0.656, 0.1234, 0.665, 0.3434}
    fmt.Println("Original:", arr)
    sorted := BucketSort(arr, 3)
    fmt.Println("Sorted:", sorted)
}

Dry Run Example

Array: [0.42, 0.32, 0.33, 0.52], Buckets: 2

Range: [0-0.5], [0.5-1.0]
Bucket 0: [0.42, 0.32, 0.33] โ†’ [0.32, 0.33, 0.42]
Bucket 1: [0.52] โ†’ [0.52]
Result: [0.32, 0.33, 0.42, 0.52]

Complexity

  • Time: Average O(n + k), Worst O(nยฒ)
  • Space: O(n + k)
  • Stable: Yes (if stable sort used for buckets)
  • In-place: No

When to Use

โœ… Use When: Uniformly distributed data, floating-point numbers
โŒ Avoid When: Non-uniform distribution, limited memory


Algorithm Comparison Summary

AlgorithmTime ComplexitySpaceStableIn-placeBest Use Case
Bubble SortO(nยฒ)O(1)โœ…โœ…Educational, tiny datasets
Selection SortO(nยฒ)O(1)โŒโœ…Memory limited, small data
Insertion SortO(nยฒ)O(1)โœ…โœ…Small/nearly sorted data
Merge SortO(n log n)O(n)โœ…โŒLarge datasets, stability needed
Quick SortO(n log n)*O(log n)โŒโœ…General purpose, average case
Counting SortO(n + k)O(k)โœ…โŒSmall integer range
Bucket SortO(n + k)O(n)โœ…โŒUniform distribution

*Average case, worst case O(nยฒ)

When to Use Which Algorithm?

Decision Tree

Data size?
โ”œโ”€โ”€ Small (< 50) โ†’ Insertion Sort
โ”œโ”€โ”€ Medium (50-10k) โ†’ Quick Sort
โ””โ”€โ”€ Large (> 10k) โ†’ 
    โ”œโ”€โ”€ Stability needed? โ†’ Merge Sort
    โ”œโ”€โ”€ Integer range small? โ†’ Counting Sort
    โ”œโ”€โ”€ Uniform distribution? โ†’ Bucket Sort
    โ””โ”€โ”€ Default โ†’ Quick Sort

Interview Tips

Common Questions:

  1. Q: Why is Quick Sort preferred over Merge Sort?
    A: In-place sorting, better cache performance, average O(n log n)

  2. Q: When would you choose Insertion Sort?
    A: Small datasets, nearly sorted data, online algorithms

  3. Q: Explain stability in sorting
    A: Stable sorts preserve relative order of equal elements

  4. Q: Which sort for real-time systems?
    A: Merge Sort (guaranteed O(n log n)) or Heap Sort

Key Points to Remember:

  • Understand trade-offs: Time vs Space vs Stability
  • Know when each algorithm shines
  • Practice dry runs for better understanding
  • Consider real-world constraints (memory, stability, etc.)

Practice Problems

Easy:

  1. Implement bubble sort with optimization
  2. Sort array using selection sort
  3. Insertion sort for linked list

Medium:
4. Merge k sorted arrays 5. Quick sort with random pivot 6. Sort colors (Dutch flag problem)

Hard: 7. External sorting for large files 8. Sort array with 3 different elements 9. Implement hybrid sort (Intro sort)

Real-World Applications

  • Databases: Query optimization, indexing
  • Operating Systems: Process scheduling, memory management
  • Graphics: Z-buffer sorting, polygon rendering
  • Search Engines: PageRank sorting, relevance scoring
  • Machine Learning: Data preprocessing, feature selection

Conclusion

Understanding sorting algorithms is crucial for:

  • Technical interviews: Common questions and problem-solving
  • Algorithm design: Choosing right approach for constraints
  • Performance optimization: Time/space trade-offs
  • System design: Large-scale data processing

Key Takeaways:

  • No single best algorithm - depends on constraints
  • Understand trade-offs between time, space, and stability
  • Practice implementations and dry runs
  • Know when to use each algorithm in real scenarios

Master these fundamentals to excel in technical interviews and build efficient systems!