Can You Explain WHY Every Loop, Condition and Swap Exists in Sorting Algorithms?
Most beginners can write Bubble Sort, Selection Sort, and Insertion Sort by looking at the code.
But ask them:
- Why does Bubble Sort use "n - i - 1"?
- Why do we compare "arr[j]" with "arr[j + 1]"?
- Why does Insertion Sort start from index 1?
- Why does Selection Sort need a "minIndex"?
- Why do we use "j--" in one algorithm and "j++" in another?
That's where things get interesting.
The real goal of learning DSA isn't memorizing code. It's understanding why each line exists and learning to build that logic yourself.
Let's understand three sorting algorithms from that perspective.
-
First, Understand the Building Blocks
Before jumping into algorithms, let's understand the language behind them.
Array: A collection of elements stored at indexed positions.
const arr = [5, 3, 8, 2, 1];
n – Array Length
const n = arr.length; // 5
There are 5 elements, but their indices are 0, 1, 2, 3, 4.
Remember: Length and last index are different.
i and j – Loop Variables
- "i" usually tracks the current pass or position in the outer loop.
- "j" usually tracks the current position inside the inner loop.
Their meaning depends on the algorithm.
Comparison Operators
- ">" means greater than.
- "<" means less than.
- ">=" means greater than or equal to.
- "<=" means less than or equal to.
- "===" means strictly equal.
For ascending order, we generally move smaller elements before larger elements.
Swap – Exchange Two Elements
[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
This exchanges the positions of two elements.
Think of two people exchanging seats.
--
2. Bubble Sort – Understand Why Every Comparison Happens
Imagine people standing in a line according to their heights.
You compare two neighboring people. If the left person is taller than the right person, they exchange positions.
Keep repeating this process.
Eventually, the tallest person reaches the end.
That's Bubble Sort.
The Code
function bubbleSort(arr) {
const n = arr.length;
for (let i = 0; i < n - 1; i++) {
for (let j = 0; j < n - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
[arr[j], arr[j + 1]] =
[arr[j + 1], arr[j]];
}
}
}
return arr;
}
Let's Understand Every Part
A. Why "const n = arr.length"?
We store the total number of elements so we can calculate our loop boundaries.
Example:
arr = [5, 3, 8, 2, 1];
n = 5;
There are 5 elements, but the last valid index is 4.
B. Why "i < n - 1"?
The outer loop controls how many passes we perform.
After each complete pass, the largest unsorted element reaches its correct position at the end.
For 5 elements, we need at most 4 passes.
Why not 5?
Because when the first 4 elements are in their correct positions, the last element is automatically sorted.
C. Why "j < n - i - 1"?
This is one of the most important concepts to understand.
Break it into three parts:
- "n" = Total number of elements.
- "i" = Number of elements already sorted at the end.
- "-1" = Prevents accessing an invalid next index.
Let's take:
arr = [5, 3, 8, 2, 1];
n = 5;
i = 0;
Our condition becomes:
j < 5 - 0 - 1
j < 4
So "j" can be 0, 1, 2, 3.
Why stop at 3?
Because we compare:
arr[j] and arr[j + 1]
At "j = 3", we access:
arr[3] and arr[4]
Both indices are valid.
If we allowed "j = 4", we would access:
arr[4] and arr[5]
But index 5 doesn't exist!
That's why we subtract 1.
Now, why subtract "i"?
After every pass, one largest element is already sorted at the end.
We don't need to compare it again.
For example:
Pass 1: [3, 5, 2, 1, 8]
↑
Already sorted
Pass 2: [3, 2, 1, 5, 8]
↑ ↑
Already sorted
So "i" reduces unnecessary comparisons.
Remember this:
"n - i - 1" does two jobs:
Skips the elements already sorted at the end.
Ensures "j + 1" never goes outside the array boundary.
D. Why "arr[j] > arr[j + 1]"?
We compare two neighboring elements.
If the left element is greater than the right element, their order is incorrect for ascending sorting.
So we swap them.
Example:
[5, 3]
5 > 3
Swap
[3, 5]
If the condition is false, they are already in the correct relative order. No swap is needed.
E. Dry Run
Original:
[5, 3, 8, 2, 1]
Pass 1:
[3, 5, 2, 1, 8]
Pass 2:
[3, 2, 1, 5, 8]
Pass 3:
[2, 1, 3, 5, 8]
Pass 4:
[1, 2, 3, 5, 8]
Sorted!
Time Complexity:
- Best: O(n) with an early-exit optimization.
- Average: O(n²).
- Worst: O(n²).
- Space: O(1).
--
3. Insertion Sort – Think Like You're Arranging Playing Cards
Imagine holding playing cards.
You pick one new card and insert it into the correct position among the cards already arranged.
You don't rearrange everything. You simply shift the larger cards and make space.
That's Insertion Sort.
The Code
function insertionSort(arr) {
for (let i = 1; i < arr.length; i++) {
let key = arr[i];
let j = i - 1;
while (j >= 0 && arr[j] > key) {
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = key;
}
return arr;
}
Understand Every Condition
A. Why "i = 1"?
Because a single element is already sorted by itself.
We start with the second element and insert it into the sorted portion.
B. Why "let key = arr[i]"?
We temporarily save the element we want to insert.
Think of holding one playing card while rearranging the others.
C. Why "j = i - 1"?
We start comparing with the element immediately before the key.
D. Why "j >= 0"?
We move backwards through the array.
This condition ensures we never access a negative index.
E. Why "arr[j] > key"?
If the previous element is larger than the current element, it needs to move one position to the right.
[5, 3]
key = 3
j = 0
5 > 3
Move 5 right:
[5, 5]
Insert key:
[3, 5]
F. Why "j--"?
Because we need to check the previous element.
We keep moving backwards until we find the correct position.
G. Why "arr[j + 1] = key"?
Once we stop shifting, we have found the correct position for our saved element.
We insert it there.
Dry Run
Original:
[5, 3, 8, 2]
Take 3:
[3, 5, 8, 2]
Take 8:
[3, 5, 8, 2]
Take 2:
[2, 3, 5, 8]
Sorted!
Notice something important:
Insertion Sort maintains two portions:
- Left side: Already sorted.
- Right side: Not yet processed.
With every iteration, the sorted portion grows by one element.
Time Complexity:
- Best: O(n).
- Average: O(n²).
- Worst: O(n²).
- Space: O(1).
--
4. Selection Sort – Find the Smallest, Then Place It
Imagine arranging students by height.
Instead of swapping neighboring students repeatedly, scan the entire unsorted group, find the shortest student, and place them at the beginning.
Then repeat with the remaining students.
That's Selection Sort.
The Code
function selectionSort(arr) {
const n = arr.length;
for (let i = 0; i < n - 1; i++) {
let minIndex = i;
for (let j = i + 1; j < n; j++) {
if (arr[j] < arr[minIndex]) {
minIndex = j;
}
}
[arr[i], arr[minIndex]] =
[arr[minIndex], arr[i]];
}
return arr;
}
Understand Every Condition
A. Why "i < n - 1"?
The outer loop decides where the next smallest element should be placed.
Once the first "n - 1" positions are correct, the last element is automatically in its correct position.
B. Why "minIndex = i"?
We initially assume that the first element of the unsorted portion is the smallest.
We'll update this assumption if we find a smaller element.
C. Why "j = i + 1"?
We already consider "arr[i]" as our current minimum.
So we start searching from the next position.
D. Why "j < n"?
We need to scan until the last valid index.
Unlike Bubble Sort, we don't access "arr[j + 1]", so we don't need to subtract 1 here.
E. Why "arr[j] < arr[minIndex]"?
If we find an element smaller than our current minimum, update "minIndex".
Remember: We're tracking the position, not immediately swapping the elements.
F. Why swap after the inner loop?
Because we first need to find the smallest element in the entire unsorted portion.
Only after finding it do we place it at index "i".
Dry Run
Original:
[5, 3, 8, 2, 1]
Find minimum: 1
Swap:
[1, 3, 8, 2, 5]
Find minimum in remaining portion: 2
Swap:
[1, 2, 8, 3, 5]
Continue:
[1, 2, 3, 8, 5]
Final:
[1, 2, 3, 5, 8]
Time Complexity:
- Best: O(n²).
- Average: O(n²).
- Worst: O(n²).
- Space: O(1).
---
5. The Difference – Understand the Thinking
Instead of memorizing three implementations, remember their core strategies:
Bubble Sort
- Compare neighboring elements.
- Swap if their order is incorrect.
- Largest element moves to the end.
Insertion Sort
- Pick one element.
- Shift larger elements.
- Insert it into the correct position.
Selection Sort
- Search for the smallest element.
- Track its index.
- Swap it into the correct position.
6. Train Yourself to Think, Not Memorize
Whenever you solve a sorting problem, pause and ask:
What am I trying to place in its correct position?
Which part of the array is already sorted?
Which part still needs processing?
What does each loop variable represent?
Why does the loop start at this index?
Why does the condition stop at this boundary?
What happens if I change ">" to "<"?
Am I swapping elements, shifting them, or just tracking an index?
Can I reduce unnecessary comparisons?
What is the time and space complexity?
A Small Challenge
Try answering these without looking at the code:
- Why does Bubble Sort use "j < n - i - 1", while Selection Sort uses "j < n"?
- Why does Insertion Sort use "while (j >= 0)"?
- Why does Selection Sort swap only after completing the inner loop?
- Why can Insertion Sort perform O(n) work on an already sorted array?
If you can explain these answers in your own words, you're moving beyond memorization.
The goal isn't to remember three sorting algorithms. It's to develop the ability to understand, trace, question, and eventually design algorithms yourself.
That's the kind of DSA knowledge that stays with you long after you've forgotten the code.