Jump to section
Max heapify, also called sift-down, moves a value down a binary heap until it is larger than both children. Building a max heap from an unsorted array runs it on every parent, last to first: O(n) time, O(1) extra space. Heap sort then repeatedly swaps out the root, for O(n log n) in total.
The Problem
A max heap is a complete binary tree where every parent is at least as large as its children, so the largest value is always at the root. It is stored in a plain array: the children of index i are at 2i + 1 and 2i + 2, and its parent is at (i โ 1) / 2 rounded down.
The interview task usually comes in three parts: write max heapify (sift a value down), use it to build a max heap from an unsorted array in place, and sometimes finish with heap sort.
Example 1
Input: [3, 9, 2, 1, 4, 5]. After building a max heap: [9, 4, 5, 1, 3, 2]. 9 is the root, and each parent is larger than its children: 9 over 4 and 5, 4 over 1 and 3, 5 over 2.
Example 2
Heap sort on the same input gives [1, 2, 3, 4, 5, 9]. Each step swaps the root (the current maximum) to the end of the array and sifts the new root down within the shrinking heap.
Assumptions to confirm
- Max heap or min heap? The logic is identical with the comparison flipped.
- In place, or may you allocate a new array?
- May you use the language's heap library, or should you write the operations yourself? For this question, usually yourself.
The Naive Approach
The obvious way to build a heap is to insert values one at a time, sifting each new value up from the bottom. Each insert costs up to O(log n), so building takes O(n log n). It is correct and worth mentioning, but the bottom-up method below is faster, and explaining why is the part interviewers listen for.
Ready to put this into practice?
Practice this with MockIF →Still applying for jobs? Let Auto Apply do it
The Optimal Approach: Bottom-Up Heapify
Write sift-down for one index
Compare the value with its children. If a child is larger, swap with the larger child and continue from that child's position. Stop when neither child is larger or there are no children.
Skip the leaves
Indexes from n / 2 onwards have no children, so they are already valid one-element heaps.
Sift down every parent, from the last parent to the root
Starting at n / 2 โ 1 and moving backwards means each subtree is already a heap when you fix its root.
For heap sort, shrink the heap one step at a time
Swap the root with the last element of the heap, reduce the heap size by one, and sift the new root down. Repeat until one element remains.
Python and TypeScript Solutions
Python
def sift_down(a: list[int], i: int, size: int) -> None:
"""Move a[i] down until both children are smaller (max heap)."""
while True:
largest = i
left, right = 2 * i + 1, 2 * i + 2
if left < size and a[left] > a[largest]:
largest = left
if right < size and a[right] > a[largest]:
largest = right
if largest == i:
return
a[i], a[largest] = a[largest], a[i]
i = largest
def build_max_heap(a: list[int]) -> None:
"""Heapify in place in O(n): sift down every parent, last parent first."""
for i in range(len(a) // 2 - 1, -1, -1):
sift_down(a, i, len(a))
def heap_sort(a: list[int]) -> None:
build_max_heap(a)
for end in range(len(a) - 1, 0, -1):
a[0], a[end] = a[end], a[0] # move the max to its final place
sift_down(a, 0, end)
TypeScript
// Move a[i] down until both children are smaller (max heap).
function siftDown(a: number[], i: number, size: number): void {
while (true) {
let largest = i;
const left = 2 * i + 1;
const right = 2 * i + 2;
if (left < size && a[left] > a[largest]) largest = left;
if (right < size && a[right] > a[largest]) largest = right;
if (largest === i) return;
[a[i], a[largest]] = [a[largest], a[i]];
i = largest;
}
}
// Heapify in place in O(n): sift down every parent, last parent first.
function buildMaxHeap(a: number[]): void {
for (let i = Math.floor(a.length / 2) - 1; i >= 0; i--) siftDown(a, i, a.length);
}
function heapSort(a: number[]): void {
buildMaxHeap(a);
for (let end = a.length - 1; end > 0; end--) {
[a[0], a[end]] = [a[end], a[0]]; // move the max to its final place
siftDown(a, 0, end);
}
}
Python's heapq is a min-heap
In day-to-day code you would use the standard library. Python's heapq only provides a min-heap, so the usual trick for a max heap is to store negated numbers. heapq.heapify also runs in O(n).
import heapq
nums = [3, 9, 2, 1, 4, 5]
heap = [-n for n in nums] # heapq is a min-heap, so store negatives
heapq.heapify(heap) # O(n)
largest = -heap[0] # 9
JavaScript and TypeScript have no built-in heap, which is why interviewers often ask TypeScript candidates to write one.
Ready to put this into practice?
Practice this with MockIF →Still applying for jobs? Let Auto Apply do it
Complexity Compared
| Operation | Time | Extra space |
|---|---|---|
| Sift down one index | O(log n) | O(1) |
| Build by inserting one at a time | O(n log n) | O(1) in place |
| Build bottom-up (heapify) | O(n) | O(1) |
| Heap sort | O(n log n) | O(1) |
| Read the maximum | O(1) | O(1) |
Why Building a Heap Is O(n), Not O(n log n)
This is the follow-up question most candidates struggle with. Sift-down costs time proportional to a node's height, not the tree's full height. Half the nodes are leaves and cost nothing, a quarter sit one level up and move at most one step, an eighth move at most two steps, and so on. Adding that up gives a total that stays below a small constant times n, so bottom-up heapify is linear. Inserting values one at a time is slower because every insert can travel the full height of the tree.
Ready to put this into practice?
Practice this with MockIF →Still applying for jobs? Let Auto Apply do it
How to Talk Through It in an Interview
- Clarify: "Max heap, in place, and I write the operations myself rather than using a library?"
- Explain the layout: "The heap lives in an array: children of i are 2i + 1 and 2i + 2."
- Naive: "Inserting one value at a time works, at O(n log n)."
- Optimise: "Bottom-up heapify sifts down each parent from the last one, which is O(n) because most nodes are near the bottom."
- Test: an empty array, one element, all-equal values, an already sorted array and a reverse sorted one.
Edge Cases and Follow-Ups
Off-by-one in the child check
Always compare child indexes with the current heap size, not the array length. Heap sort shrinks the heap while the array stays the same length.
Equal values
Use strict greater-than when choosing a child to swap with, so equal values do not swap pointlessly or loop.
Follow-up: k largest elements
Keep a min-heap of size k and push each value, popping when it grows past k. That is O(n log k).
Follow-up: merge k sorted lists
A min-heap holding the head of each list gives the next smallest value in O(log k).
Follow-up: is heap sort stable?
No. Swapping the root to the end can reorder equal values, unlike merge sort.
Ready to put this into practice?
Practice this with MockIF →Still applying for jobs? Let Auto Apply do it
Practise It in a Mock Coding Round
Reading a solution is not the same as producing it with someone watching. In a real round you have to clarify the problem, say the brute force out loud, and keep talking while you type. MockIF runs coding rounds with a voice AI interviewer in a real in-browser editor, in Python and TypeScript. It runs your code against tests, asks you to explain your approach, follows up on edge cases and complexity, then scores the round and gives you specific feedback. A session costs 2 credits. It is free to start with 5 credits, then monthly or 3-monthly plans, or one-off credit packs (see pricing). Read more about how a mock coding interview works.
Frequently Asked Questions
What does max heapify do?
Why is building a heap O(n)?
Is Python heapq a min-heap or a max heap?
What is the difference between sift up and sift down?
What is heap sort used for in interviews?
Stop Preparing in Your Head. Start Practicing Out Loud.
Drop your resume, add a job description, and get a mock interview like the real thing.
Start Free Mock Interview →Still applying for jobs? Let Auto Apply do it for you.