Bucket sort is a distribution-based sorting algorithm. Instead of comparing elements, it spreads them into "buckets" based on their value, sorts each bucket individually, then concatenates the results. When data is uniformly distributed, it achieves average O(n) time — faster than comparison-based sorts which are bounded by O(n log n).


How Bucket Sort Works

  1. Find the minimum and maximum values in the input
  2. Create k empty buckets covering the value range
  3. Distribute each element into its appropriate bucket
  4. Sort each bucket individually
  5. Concatenate all buckets in order to produce the sorted output

Visual Walkthrough

Input: [42, 13, 75, 29, 88, 5, 61, 37] — min=5, max=88, 4 buckets

Range per bucket = (88 - 5 + 1) / 4 ≈ 21

Bucket 0 [5–25]:   [13, 5]      → sorted: [5, 13]
Bucket 1 [26–46]:  [42, 29, 37] → sorted: [29, 37, 42]
Bucket 2 [47–67]:  [61]         → sorted: [61]
Bucket 3 [68–88]:  [75, 88]     → sorted: [75, 88]

Final result: [5, 13, 29, 37, 42, 61, 75, 88] ✓

Java Implementation

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class BucketSort {

    public static void bucketSort(int[] arr) {
        if (arr == null || arr.length <= 1) return;

        int min = arr[0], max = arr[0];
        for (int val : arr) {
            if (val < min) min = val;
            if (val > max) max = val;
        }

        if (min == max) return; // all values are equal

        int bucketCount = arr.length;
        List<List<Integer>> buckets = new ArrayList<>(bucketCount);
        for (int i = 0; i < bucketCount; i++) {
            buckets.add(new ArrayList<>());
        }

        double divisor = (double)(max - min + 1) / bucketCount;
        for (int val : arr) {
            int bucketIndex = (int)((val - min) / divisor);
            // Clamp to avoid floating-point edge cases
            if (bucketIndex >= bucketCount) bucketIndex = bucketCount - 1;
            buckets.get(bucketIndex).add(val);
        }

        int index = 0;
        for (List<Integer> bucket : buckets) {
            Collections.sort(bucket);
            for (int val : bucket) {
                arr[index++] = val;
            }
        }
    }

    public static void main(String[] args) {
        int[] data = {42, 13, 75, 29, 88, 5, 61, 37};

        System.out.print("Before: ");
        for (int v : data) System.out.print(v + " ");

        bucketSort(data);

        System.out.print("\nAfter:  ");
        for (int v : data) System.out.print(v + " ");
    }
}

Output:

Before: 42 13 75 29 88 5 61 37
After:  5 13 29 37 42 61 75 88

Floating-Point Variant

Bucket sort is classically defined for floating-point values in [0, 1):

public static void bucketSortFloat(double[] arr) {
    int n = arr.length;
    List<List<Double>> buckets = new ArrayList<>(n);
    for (int i = 0; i < n; i++) buckets.add(new ArrayList<>());

    for (double val : arr) {
        int index = (int)(val * n);
        if (index == n) index = n - 1;
        buckets.get(index).add(val);
    }

    int pos = 0;
    for (List<Double> bucket : buckets) {
        Collections.sort(bucket);
        for (double val : bucket) arr[pos++] = val;
    }
}

// Usage:
double[] data = {0.72, 0.17, 0.39, 0.55, 0.14, 0.81};
bucketSortFloat(data);
// Result: 0.14 0.17 0.39 0.55 0.72 0.81

Time and Space Complexity

CaseTimeNotes
BestO(n + k)Uniform distribution, ~1 element per bucket
AverageO(n + k)Uniformly distributed input
WorstO(n²)All elements in one bucket
SpaceO(n + k)n elements + k bucket lists

The worst case happens when input is highly skewed — all elements cluster in one bucket and the inner sort dominates.


When to Use Bucket Sort

Good fit:

  • Input values are uniformly distributed across a known finite range
  • Sorting floating-point numbers between 0 and 1
  • You need average O(n) performance and can afford O(n + k) extra memory

Poor fit:

  • Input is skewed (bucket sort degrades to O(n²))
  • The value range is unknown or extremely large
  • Memory is constrained
  • You need a guaranteed worst-case bound — use merge sort instead

Bucket Sort vs. Other Algorithms

AlgorithmAverageWorstIn-placeStable
Bucket SortO(n + k)O(n²)NoYes
Counting SortO(n + k)O(n + k)NoYes
Radix SortO(nk)O(nk)NoYes
Merge SortO(n log n)O(n log n)NoYes
Quick SortO(n log n)O(n²)YesNo

For production use, Java's Arrays.sort() uses dual-pivot quicksort for primitives and Timsort for objects — both highly optimized. Implement bucket sort when you have a measured bottleneck and know your data distribution is uniform.

Sorting a 1D array is a one‑liner with Arrays.sort(arr). But what about a 2D array where you need to sort rows by the first column, then use the second column as a tiebreaker? Java's Arrays.sort() accepts a Comparator for this exact scenario, and there are three styles to write one — a named class, an anonymous class, and a lambda. Let's walk through all of them.

๐Ÿ“Š From my experience: I've sorted thousands of multidimensional arrays in my career – from processing CSV data to building scheduling algorithms. The ability to sort by multiple columns is one of those skills that looks simple but is incredibly powerful once you master it. Knowing the comparator patterns saved me hours of manual sorting and bug hunting.

Why This Matters – A Personal Story

Early in my career, I was building a schedule optimiser for a logistics company. We had a 2D array of shipment records: [priority, weight, destinationId]. We needed to sort by priority, then weight, then destination – and I spent hours writing custom sorting loops, only to introduce subtle bugs.

Then a senior developer showed me Arrays.sort() with a comparator. It was a revelation. What I'd been writing in 50 lines of error‑prune code became a clean, one‑line lambda. That's when I realised that the Java collections framework has already solved most of your problems – you just need to know how to use it.


The Problem

Given a 2D array of integers where each row has two values, sort rows by the first column in ascending order. When two rows have the same first value, sort by the second column in ascending order:

int[][] arr = {
    {20, 25},
    {10, 15},
    {10, 25},
    {20, 15}
};

// Expected after sorting:
// {10, 15}  ← first column = 10, second = 15
// {10, 25}  ← first column = 10, second = 25
// {20, 15}  ← first column = 20, second = 15
// {20, 25}  ← first column = 20, second = 25

The Key: What Arrays.sort() Needs

Arrays.sort(T[] array, Comparator<T> comparator) sorts an array of objects using a custom comparison function. Since each row of a 2D array is an int[], you need a Comparator<int[]> that compares two rows.

The comparator returns:

  • A negative number if the first argument should come before the second
  • Zero if they are equal
  • A positive number if the first argument should come after the second
๐Ÿ’ก The mental model I use: I think of the comparator as a "rule book" that decides which row should go first. If I'm comparing two rows, I ask: "Which one would I want to see first in a printed table?" That makes the logic intuitive.

Style 1: Named Comparator Class

Cleanest for reusable or complex comparators:

import java.util.Arrays;
import java.util.Comparator;

class MultiDimArrayComparator implements Comparator<int[]> {
    @Override
    public int compare(int[] m, int[] n) {
        if (m[0] == n[0]) {
            return m[1] - n[1]; // tiebreak on second column
        }
        return m[0] - n[0]; // primary sort on first column
    }
}

public class SortDemo {
    public static void main(String[] args) {
        int[][] arr = {{20,25},{10,15},{10,25},{20,15}};
        Arrays.sort(arr, new MultiDimArrayComparator());

        for (int[] row : arr) {
            System.out.println(row[0] + " " + row[1]);
        }
    }
}
10 15
10 25
20 15
20 25
๐Ÿ“ฆ When I use this: If I need the same comparator in multiple places, I create a named class. It's also useful for unit testing – I can test the comparator logic in isolation. This is the most maintainable approach for complex sorting rules.

Style 2: Anonymous Class

When you only need the comparator in one place and don't want to create a separate class file:

Arrays.sort(arr, new Comparator<int[]>() {
    @Override
    public int compare(int[] m, int[] n) {
        return m[0] == n[0] ? m[1] - n[1] : m[0] - n[0];
    }
});

Same logic, less boilerplate. The ternary operator keeps it compact.

๐Ÿ‘ด A blast from the past: Before Java 8, this was the most common way to write inline comparators. I've written hundreds of these. Now with lambdas, I rarely use anonymous classes for functional interfaces – but they're still a valid style, especially if you're stuck on older Java versions.

Style 3: Lambda (Java 8+)

Since Comparator is a functional interface, you can express it as a lambda for maximum conciseness:

Arrays.sort(arr, (m, n) -> m[0] == n[0] ? m[1] - n[1] : m[0] - n[0]);

This is the style you'll see most often in modern Java code. All three styles produce identical results.

⭐ My go‑to: For 90% of cases, the lambda is perfect. It's concise, readable, and keeps the sorting logic right where it's used. I only reach for the named class when the comparator is complex enough to deserve its own type, or when I need to reuse it across the codebase.

Sorting in Descending Order

Just reverse the comparison — swap m and n:

// Descending by first column, descending by second on tie
Arrays.sort(arr, (m, n) -> n[0] == m[0] ? n[1] - m[1] : n[0] - m[0]);

for (int[] row : arr) System.out.println(row[0] + " " + row[1]);
// 20 25
// 20 15
// 10 25
// 10 15
๐Ÿ” A neat trick: To reverse the entire sort order, you can use Comparator.reverseOrder() – but that only works if your comparator is already defined. I often just swap the operands in the lambda – it's explicit and easy to read.

Using Comparator.comparingInt() — The Fluent Way

Java's Comparator class has factory methods for building multi‑level comparators cleanly:

import java.util.Comparator;

Arrays.sort(arr,
    Comparator.comparingInt((int[] row) -> row[0])
              .thenComparingInt(row -> row[1])
);

for (int[] row : arr) System.out.println(row[0] + " " + row[1]);
// 10 15
// 10 25
// 20 15
// 20 25

The .thenComparingInt() chaining makes multi‑column sorting read like English: "sort by column 0, then by column 1." This is particularly clean when you have three or more columns to sort by.

✨ My favourite for complex sorts: When I have three or more columns, the fluent API is a lifesaver. I've used it for sorting by [lastName, firstName, middleInitial] and for [priority, deadline, id] in scheduling systems. It reads like a specification, not code – which is the highest compliment I can give to a sorting method.

Real‑World Example: Sorting Meeting Intervals

A classic interview problem — given a list of meeting intervals [start, end], sort them by start time (and by end time on ties) to process them chronologically:

int[][] meetings = {
    {9, 11},
    {8, 10},
    {9, 10},
    {14, 15},
    {8, 9}
};

Arrays.sort(meetings, (a, b) -> a[0] != b[0] ? a[0] - b[0] : a[1] - b[1]);

System.out.println("Sorted meetings:");
for (int[] m : meetings) {
    System.out.printf("[%d, %d]%n", m[0], m[1]);
}
// [8, 9]
// [8, 10]
// [9, 10]
// [9, 11]
// [14, 15]
๐Ÿงช Interview tip: This exact pattern appears in countless interview problems – merge intervals, calendar availability, and scheduling. I've used it in real‑world code, and I always recommend it to junior developers learning data structures. Mastering multi‑column sorting is a quick win in both interviews and production code.

A Word on Integer Overflow

The subtraction trick (m[0] - n[0]) is a fast way to write a comparator, but it can overflow when values are extreme (e.g., Integer.MAX_VALUE - Integer.MIN_VALUE overflows). For interview problems with constrained inputs it's fine, but for production code prefer:

// Safe — no overflow risk
Arrays.sort(arr, (m, n) -> Integer.compare(m[0], n[0]) != 0
    ? Integer.compare(m[0], n[0])
    : Integer.compare(m[1], n[1]));

// Or more concisely using Comparator chain:
Arrays.sort(arr, Comparator.comparingInt((int[] r) -> r[0]).thenComparingInt(r -> r[1]));
๐Ÿšจ A bug I've seen in production: We had a comparator that used subtraction, and one day the input contained Integer.MIN_VALUE. The overflow produced a positive number, and the sort order was completely wrong. This caused a financial report to be misordered, and we had to issue a correction. Since then, I always use Integer.compare() for safety – it's a few more characters but worth it.

Comparing with Strings and Other Types

The same pattern works for arrays of strings, objects, or any type. Here's a 2D array of strings sorted by the first column lexicographically, then by the second:

String[][] people = {
    {"Alice", "Engineer"},
    {"Bob", "Manager"},
    {"Alice", "Designer"},
    {"Charlie", "Architect"}
};

Arrays.sort(people, Comparator
    .comparing((String[] row) -> row[0])
    .thenComparing(row -> row[1])
);

for (String[] p : people) {
    System.out.println(p[0] + " - " + p[1]);
}
// Alice - Designer
// Alice - Engineer
// Bob - Manager
// Charlie - Architect

The fluent Comparator.comparing() works with any type that implements Comparable (like String), and you can use .thenComparing() for subsequent columns.


How I Teach This to Juniors

When I mentor junior developers, I break this down into three steps:

  1. Understand that each row is an object. In a 2D array int[][] arr, each row is an int[]. So you're sorting an array of objects – that's why you need a Comparator.
  2. Write the comparison logic in plain English. For example: "If two rows have different first elements, put the smaller first element first. If they have the same first element, put the smaller second element first." Then translate that to code.
  3. Choose the right style. For a one‑off sort, use a lambda or fluent comparator. For a reusable rule, use a named class.

Once they get this, they're no longer afraid of custom sorting – and it opens up a whole new world of data manipulation.


Summary

To sort a 2D array in Java, pass a Comparator<int[]> to Arrays.sort(). The comparator receives two rows and returns negative, zero, or positive to indicate their relative order. For multi‑column sorting (primary column first, secondary as tiebreaker), use a ternary expression or chain Comparator.comparingInt().thenComparingInt().

Key takeaways:

  • Use a lambda for inline sorting – it's concise and readable.
  • Use Comparator.comparingInt() for multi‑column sorting – it's fluent and safe.
  • Avoid the subtraction trick for large integers – use Integer.compare() to prevent overflow.
  • For reusable logic, extract the comparator to a named class or a static field.
  • Practice with real examples – meeting intervals, priority queues, and CSV processing.

This is one of those skills that pays off every time you work with tabular data. I've used it in analytics, logistics, and financial systems – and I keep coming back to it. Master this, and you'll never waste time writing manual sorting loops again.


Happy coding – and may your data always be sorted as you expect!


How to Sort HashMap in java

This article covers some of the aspects of sorting Map in Java. Java Map comes in different flavours, like HashMap, TreeMap, LinkedHashMap etc. TreeMap for instance will sort the data based on the keys. We can also pass a custom Comparator to sort based on the keys as per the custom sort algorithm.
We can have two requirements in terms of sorting, first one is sorting by Keys and second is Sorting by Values. Following examples demonstrates few approaches for sorting by key and sorting by value. Following examples uses Java Lambda and Stream api to sort Java HashMap instance.

Sort by Value

Map sort by value in revere order
Following code snippet sorts the map based on value in reverse order and populates a new map reverseSortedMap from the data.
//LinkedHashMap preserve the ordering of elements in which they are inserted
Map reverseSortedMap =  new LinkedHashMap();
//Use Comparator.reverseOrder() for reverse ordering
map.entrySet()
    .stream()
    .sorted(Map.Entry.comparingByValue(Comparator.reverseOrder())) 
    .forEachOrdered(x -> reverseSortedMap.put(x.getKey(), x.getValue()));

Sort By Key

Map sort by key in revere order
Following code snippet sorts the map based on the keys in reverse order.
//LinkedHashMap preserve the ordering of elements in which they are inserted
Map reverseSortedMapByKey =  new LinkedHashMap();;
//Use Comparator.reverseOrder() for reverse ordering
map.entrySet()
    .stream()
    .sorted(Map.Entry.comparingByKey(Comparator.reverseOrder())) 
    .forEachOrdered(x -> reverseSortedMapByKey.put(x.getKey(), x.getValue()));
 

System.out.println("Sorted by Value Map : " + reverseSortedMapByKey);

Full Example

MapSortJava
public class MapSortJava {

  public static void main(String[] args) {

    Map<Integer, String> map = new HashMap<Integer, String>();
    map.put(101, "Tokyo");
    map.put(3, "New York");
    map.put(2, "San Francisco");
    map.put(14, "Los Angels");
    map.put(5, "Austin");

    System.out.println("Unsorted Map : " + map);
    
   // LinkedHashMap preserve the ordering of elements in which they are inserted
    Map<Integer, String> reverseSortedMap = new LinkedHashMap<Integer, String>();;
    // Use Comparator.reverseOrder() for reverse ordering
    map.entrySet().stream().sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
        .forEachOrdered(x -> reverseSortedMap.put(x.getKey(), x.getValue()));
    System.out.println("Sorted by Value Map : " + reverseSortedMap);


    // LinkedHashMap preserve the ordering of elements in which they are inserted
    Map<Integer, String> reverseSortedMapByKey = new LinkedHashMap<Integer, String>();;
    // Use Comparator.reverseOrder() for reverse ordering
    map.entrySet().stream().sorted(Map.Entry.comparingByKey(Comparator.reverseOrder()))
        .forEachOrdered(x -> reverseSortedMapByKey.put(x.getKey(), x.getValue()));
    System.out.println("Sorted by Value Map : " + reverseSortedMapByKey);

  }
}
Output
Unsorted Map : 2 ==> San Francisco 3 ==> New York 101 ==> Tokyo 5 ==> Austin 14 ==> Los Angels Sorted by Value Map (Reverse) : 2 ==> San Francisco 3 ==> New York 101 ==> Tokyo 5 ==> Austin 14 ==> Los Angels Sorted by Value Map (Reverse) : 101 ==> Tokyo 14 ==> Los Angels 5 ==> Austin 3 ==> New York 2 ==> San Francisco
Summary of steps
  • Get the entry set by calling the Map.entrySet().
  • Get the entry set stream by calling stream() method.
  • Use the Map.Entry.comparingByValue() or Map.Entry.comparingByKey().
  • Call the sorted method with a Comparator.
  • call the terminal operation forEachOrdered to store the each entries in the new Map.