This is one of the most asked SQL interview topics in product-based companies.
Many candidates know GROUP BY, but very few truly understand Window Functions. Once you learn them, you'll solve problems that otherwise require messy subqueries.
Why were Window Functions invented?
Suppose we have this table.
Employees
| ID | Name | Department | Salary |
| 1 | Rahul | IT | 60000 |
| 2 | Priya | HR | 55000 |
| 3 | Amit | IT | 70000 |
| 4 | Neha | Sales | 50000 |
| 5 | Rohan | Finance | 65000 |
| 6 | Ankit | IT | 60000 |
Suppose your manager asks:
Show every employee along with their department's average salary.
Expected output:
| Name | Department | Salary | Department Avg |
| Rahul | IT | 60000 | 63333 |
| Amit | IT | 70000 | 63333 |
| Ankit | IT | 60000 | 63333 |
Can GROUP BY do this?
Problem with GROUP BY
If we write:
SELECT Department,
AVG(Salary)
FROM Employees
GROUP BY Department;
we get one row per department.
Where did Rahul go? Where is Amit? Where is Ankit?
Gone.
Why?
GROUP BY collapses multiple rows into one.
Window Functions solve this. They calculate aggregated values without collapsing rows.
Every employee remains visible.
What is a Window Function?
A Window Function performs a calculation over a set of rows, called a window, while keeping each original row in the result.
GROUP BY
6 rows
|
v
4 rows
Window Function:
6 rows
|
v
Still 6 rows
+ extra calculated column
This is the biggest difference.
OVER()
Every Window Function uses:
OVER(...)
Think of OVER() as saying:
On which set of rows should this calculation be performed?
Simplest example:
SELECT
Name,
Salary,
AVG(Salary) OVER()
FROM Employees;
Average appears beside every employee. No rows disappear.
PARTITION BY
This is the heart of Window Functions.
Suppose we want average salary per department while keeping every employee.
SELECT
Name,
Department,
Salary,
AVG(Salary)
OVER(PARTITION BY Department)
FROM Employees;
SQL internally creates partitions.
IT
Rahul
Amit
Ankit
|
v
Average
Each department becomes its own window.
GROUP BY vs PARTITION BY
GROUP BY
SELECT Department,
AVG(Salary)
FROM Employees
GROUP BY Department;
One row per department.
PARTITION BY
SELECT Name,
AVG(Salary)
OVER(PARTITION BY Department)
FROM Employees;
Employees remain.
Easy Memory Trick
GROUP BY:
Group
|
v
Collapse rows
PARTITION BY:
Group
|
v
Keep rows
ROW_NUMBER()
Assigns a unique number.
SELECT
Name,
Salary,
ROW_NUMBER()
OVER(ORDER BY Salary DESC)
FROM Employees;
Every row gets a unique number. Even ties get different numbers.
RANK()
Similar to ROW_NUMBER, but ties receive the same rank.
If salaries are:
70000
65000
60000
60000
55000
Ranks become:
1
2
3
3
5
Rank 4 is skipped.
DENSE_RANK()
Same as RANK, but does not skip numbers.
1
2
3
3
4
Difference
ROW_NUMBER:
1
2
3
4
5
RANK:
1
2
3
3
5
DENSE_RANK:
1
2
3
3
4
Easy Memory Trick
ROW_NUMBER: every row unique.RANK: competition ranking with gaps.DENSE_RANK: ranking without gaps.
Interview Question: Find the 2nd highest salary
SELECT *
FROM
(
SELECT *,
DENSE_RANK()
OVER(ORDER BY Salary DESC) AS rnk
FROM Employees
) t
WHERE rnk = 2;
This is a very common interview problem.
LAG()
Returns the previous row's value.
SELECT
Salary,
LAG(Salary)
OVER(ORDER BY Salary)
FROM Employees;
The first row has no previous row, so it returns NULL.
LEAD()
Returns the next row's value.
SELECT
Salary,
LEAD(Salary)
OVER(ORDER BY Salary)
FROM Employees;
The final row has no next row, so it returns NULL.
Running Total
Another favorite interview problem.
SELECT
Name,
Salary,
SUM(Salary)
OVER(
ORDER BY ID
) AS RunningTotal
FROM Employees;
Each row accumulates the previous total.
Common Window Functions
| Function | Purpose |
| ROW_NUMBER() | Unique row numbering |
| RANK() | Ranking with gaps |
| DENSE_RANK() | Ranking without gaps |
| AVG() OVER() | Running average or partition average |
| SUM() OVER() | Running total or partition total |
| COUNT() OVER() | Count while keeping rows |
| LAG() | Previous row |
| LEAD() | Next row |
Interview Questions
1. Difference between GROUP BY and PARTITION BY?
| GROUP BY | PARTITION BY |
| Collapses rows | Keeps rows |
| Returns one row per group | Returns all original rows |
| Used with aggregate queries | Used with window functions |
2. Difference between ROW_NUMBER(), RANK(), and DENSE_RANK()?
| Function | Duplicate Values | Gaps in Ranking |
| ROW_NUMBER() | No, every row is unique | N/A |
| RANK() | Yes | Yes |
| DENSE_RANK() | Yes | No |
3. What does OVER() do?
It defines the window of rows over which the window function should operate. You can customize the window using PARTITION BY and ORDER BY.
Mental Model
GROUP BY
Rows
|
v
Grouped
|
v
Rows disappear
Window Function
Rows
|
v
Look around neighboring rows
|
v
Calculate value
|
v
Keep every row
That's why they're called Window Functions: each row gets to look through a window of related rows to perform calculations while still remaining in the final result.
Where Are Window Functions Used?
You'll frequently use them for:
- Finding the Nth highest salary
- Leaderboards and rankings
- Running totals
- Month-over-month sales comparisons
- Previous/next event analysis
- Top-N records per department
- Detecting trends over time