Java
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented Java
-
Section 4: Collections Framework
-
Section 5: Exception Handling
-
Section 6: Generics
-
Section 7: Functional Java
-
Section 8: Concurrency
-
Section 9: I/O and NIO
-
Section 10: JVM Internals
-
Section 11: Modern Java Features
-
Section 12: Build Tools and Project Structure
-
Section 13: Testing
-
Section 14: Databases and Persistence
-
Section 15: Networking
-
Section 16: Design and Best Practices
-
Section 17: Reflection and Annotations
-
Section 18: Logging and Diagnostics
-
Section 19: Date, Time, and Internationalization
-
Section 20: Java Platform Module System
-
Section 21: Security in Java
-
Section 22: Advanced Collections and Data Structures
-
Section 23: More Concurrency Patterns
-
Section 24: Compression, Files, and System Integration
-
Section 25: GUI Programming
-
Section 26: Practical Projects
-
Section 27: Data Structures and Algorithms
-
Section 28: Interview and Algorithm Practice
-
Section 29: JSON and Data Interchange
-
Section 30: More Concurrency Utilities
-
Section 31: More Collections and Streams Practice
-
Section 32: More File and System Programming
-
Section 33: Standard Library Deep Dive
-
Section 34: More Practice and Drills
-
Section 35: More Testing and Quality
-
Section 36: More Design Patterns and Architecture
-
Section 37: Career and Ecosystem
-
Section 38: More OOP and Architecture Practice
-
Section 39: More Enterprise Concepts
-
Section 40: Advanced JavaFX
-
Section 41: More Interview Practice
235: Implementing Common Sorting Algorithms
A few years ago, I was reviewing a pull request from a junior developer who was building a custom log-analysis tool. He had decided that Collections.sort() was "too much magic" and wanted to implement his own QuickSort to show off his understanding of algorithms. It looked great on paper, but as soon as he ran it against a dataset that was already partially sorted, the entire JVM crashed with a StackOverflowError. He had picked the first element as the pivot every single time, turning his O(n log n) dream into an O(n²) nightmare that exhausted the stack. It was a humbling reminder that while we usually rely on Java's built-in sorting, knowing how these algorithms actually move data around is the only way to debug them when things go sideways.
The Simplicity and Slowness of Bubble Sort
I'll be honest with you: you will almost never use Bubble Sort in a production environment. It is the "Hello World" of sorting algorithms. However, it's the best way to visualize the concept of "swapping" and "passes." The core idea is that the largest element "bubbles up" to the end of the array with every full pass through the list.
public void bubbleSort(int[] array) {
int n = array.length;
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - i - 1; j++) {
if (array[j] > array[j + 1]) {
// Swap elements
int temp = array[j];
array[j] = array[j + 1];
array[j + 1] = temp;
}
}
}
}
The problem here is the nested loop. If you have 10,000 items, you're potentially looking at 100 million comparisons. It's agonizingly slow. But notice the n - i - 1 logic; since each pass guarantees the largest remaining element is in its final place, we don't need to check the end of the array over and over again.
Stability and Reliability with Merge Sort
When you need a guarantee that your sort is "stable"—meaning two elements with the same value stay in their original relative order—Merge Sort is your go-to. I use this mental model: it's a "divide and conquer" strategy. You split the array in half recursively until you have lists of size one, and then you merge those lists back together in the correct order.
public void mergeSort(int[] array, int left, int right) {
if (left < right) {
int mid = left + (right - left) / 2;
mergeSort(array, left, mid);
mergeSort(array, mid + 1, right);
merge(array, left, mid, right);
}
}
private void merge(int[] array, int left, int mid, int right) {
int n1 = mid - left + 1;
int n2 = right - mid;
int[] L = new int[n1];
int[] R = new int[n2];
System.arraycopy(array, left, L, 0, n1);
System.arraycopy(array, mid + 1, R, 0, n2);
int i = 0, j = 0, k = left;
while (i < n1 && j < n2) {
if (L[i] <= R[j]) {
array[k++] = L[i++];
} else {
array[k++] = R[j++];
}
}
while (i < n1) array[k++] = L[i++];
while (j < n2) array[k++] = R[j++];
}
The trade-off here is memory. Unlike Bubble Sort, Merge Sort isn't "in-place." You're creating temporary arrays (L and R) during the merge process. If you're working with a massive dataset on a memory-constrained machine, this overhead can actually become a bottleneck.
Optimizing for Speed with Quick Sort
Quick Sort is generally the fastest general-purpose sort in practice, which is why variations of it power many standard libraries. Instead of splitting the array exactly in half, it picks a "pivot" and partitions the array so that everything smaller than the pivot is on the left and everything larger is on the right.
public void quickSort(int[] array, int low, int high) {
if (low < high) {
int pi = partition(array, low, high);
quickSort(array, low, pi - 1);
quickSort(array, pi + 1, high);
}
}
private int partition(int[] array, int low, int high) {
// To avoid the StackOverflow issue I mentioned earlier,
// picking a random pivot or the middle element is much safer.
int pivot = array[high];
int i = (low - 1);
for (int j = low; j < high; j++) {
if (array[j] < pivot) {
i++;
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
int temp = array[i + 1];
array[i + 1] = array[high];
array[high] = temp;
return i + 1;
}
The magic here is that Quick Sort works in-place. No extra arrays are needed, making it very cache-friendly. Just be careful with your pivot selection. If you always pick the last element and the array is already sorted, you hit that O(n²) worst-case scenario. In a real-world scenario, I'd recommend using a "median-of-three" strategy to pick the pivot.
📋 Practical Task
Exercise: Building a Stable Transaction Ledger Sorter
Imagine you are building a financial application. You have a Transaction class with two fields: double amount and long timestamp. Your goal is to sort these transactions by amount in ascending order. However, if two transactions have the exact same amount, they must remain in the order they were originally received (their original index in the list).
Your Task:
- Create a
Transactionclass withamountandtimestamp. - Implement a
MergeSortLedgerclass that contains a methodsortTransactions(Transaction[] transactions). - You must use the Merge Sort algorithm to ensure the sort is stable.
- Write a
mainmethod that creates an array of transactions where at least two transactions have the same amount but different timestamps. Verify that the one that appeared first in the input array still appears first in the sorted array.
There are no comments for now.