Lazy propagation defers range-update work: a node stores a pending tag saying “everything below me still needs this delta applied”.
Why it matters:
Without lazy, adding to
[l..r]touches every leaf — O(n). With lazy, you stop at the first fully covered node: O(log n).
The contract:
A node with an unresolved tag still reports correct aggregates — children are only fixed when someone actually descends into them.
Core Template (Range Add + Range Sum)
Watch add(+10, [0..1]) stop at one node with a tag, then watch sum([0..3]) push the tag down only when needed. Press ▶ to animate.
⚠️ Animation & Content Notice
The animation work is not fully finished — some animations may have slight errors.
If there is a major error in the content or if the animation or content is difficult to understand, please contact us at rayyancodingschool@gmail.com.
Segment Tree with Lazy Propagation
Range add and range sum in O(log n) using deferred lazy tags.
An update stops at the first fully-covered node and stores its delta as a lazy tag instead of touching every leaf. The tag is pushed to children only when a query or update actually needs those values. Each tag is applied once per level, so both operations stay O(log n).
1
update: stop at the first fully covered node — store delta as a lazy tag
2
query: if a node holds a pending tag → push it down to children first
3
then combine answers from children
4
each tag is applied once per level → O(log n) per operation
class LazySegTree {
int n;
long[] tree, lazy;
public LazySegTree(int[] arr) {
n = arr.length;
tree = new long[4 * n];
lazy = new long[4 * n];
build(arr, 1, 0, n - 1);
}
private void build(int[] arr, int node, int lo, int hi) {
if (lo == hi) { tree[node] = arr[lo]; return; }
int mid = (lo + hi) / 2;
build(arr, node * 2, lo, mid);
build(arr, node * 2 + 1, mid + 1, hi);
tree[node] = tree[node * 2] + tree[node * 2 + 1];
}
private void apply(int node, int lo, int hi, long d) {
tree[node] += d * (hi - lo + 1);
lazy[node] += d;
}
private void push(int node, int lo, int hi) {
if (lazy[node] != 0) {
int mid = (lo + hi) / 2;
apply(node * 2, lo, mid, lazy[node]);
apply(node * 2 + 1, mid + 1, hi, lazy[node]);
lazy[node] = 0;
}
}
public void add(int l, int r, long d) {
update(1, 0, n - 1, l, r, d);
}
private void update(int node, int lo, int hi, int l, int r, long d) {
if (r < lo || hi < l) return;
if (l <= lo && hi <= r) { apply(node, lo, hi, d); return; }
push(node, lo, hi);
int mid = (lo + hi) / 2;
update(node * 2, lo, mid, l, r, d);
update(node * 2 + 1, mid + 1, hi, l, r, d);
tree[node] = tree[node * 2] + tree[node * 2 + 1];
}
public long sum(int l, int r) {
return query(1, 0, n - 1, l, r);
}
private long query(int node, int lo, int hi, int l, int r) {
if (r < lo || hi < l) return 0;
if (l <= lo && hi <= r) return tree[node];
push(node, lo, hi);
int mid = (lo + hi) / 2;
return query(node * 2, lo, mid, l, r)
+ query(node * 2 + 1, mid + 1, hi, l, r);
}
}class LazySegTree:
def __init__(self, arr):
self.n = len(arr)
self.tree = [0] * (4 * self.n)
self.lazy = [0] * (4 * self.n)
self._build(arr, 1, 0, self.n - 1)
def _build(self, arr, node, lo, hi):
if lo == hi:
self.tree[node] = arr[lo]
return
mid = (lo + hi) // 2
self._build(arr, node * 2, lo, mid)
self._build(arr, node * 2 + 1, mid + 1, hi)
self.tree[node] = self.tree[node*2] + self.tree[node*2+1]
def _apply(self, node, lo, hi, d):
self.tree[node] += d * (hi - lo + 1)
self.lazy[node] += d
def _push(self, node, lo, hi):
if self.lazy[node]:
mid = (lo + hi) // 2
self._apply(node * 2, lo, mid, self.lazy[node])
self._apply(node * 2 + 1, mid + 1, hi, self.lazy[node])
self.lazy[node] = 0
def add(self, l, r, d):
self._update(1, 0, self.n - 1, l, r, d)
def _update(self, node, lo, hi, l, r, d):
if r < lo or hi < l:
return
if l <= lo and hi <= r:
self._apply(node, lo, hi, d)
return
self._push(node, lo, hi)
mid = (lo + hi) // 2
self._update(node * 2, lo, mid, l, r, d)
self._update(node * 2 + 1, mid + 1, hi, l, r, d)
self.tree[node] = self.tree[node*2] + self.tree[node*2+1]
def sum(self, l, r):
return self._query(1, 0, self.n - 1, l, r)
def _query(self, node, lo, hi, l, r):
if r < lo or hi < l:
return 0
if l <= lo and hi <= r:
return self.tree[node]
self._push(node, lo, hi)
mid = (lo + hi) // 2
return (self._query(node * 2, lo, mid, l, r)
+ self._query(node * 2 + 1, mid + 1, hi, l, r))class LazySegTree {
int n;
vector<long long> tree, lz;
void build(vector<int>& arr, int node, int lo, int hi) {
if (lo == hi) { tree[node] = arr[lo]; return; }
int mid = (lo + hi) / 2;
build(arr, node * 2, lo, mid);
build(arr, node * 2 + 1, mid + 1, hi);
tree[node] = tree[node * 2] + tree[node * 2 + 1];
}
void apply(int node, int lo, int hi, long long d) {
tree[node] += d * (hi - lo + 1);
lz[node] += d;
}
void push(int node, int lo, int hi) {
if (lz[node]) {
int mid = (lo + hi) / 2;
apply(node * 2, lo, mid, lz[node]);
apply(node * 2 + 1, mid + 1, hi, lz[node]);
lz[node] = 0;
}
}
void update(int node, int lo, int hi, int l, int r, long long d) {
if (r < lo || hi < l) return;
if (l <= lo && hi <= r) { apply(node, lo, hi, d); return; }
push(node, lo, hi);
int mid = (lo + hi) / 2;
update(node * 2, lo, mid, l, r, d);
update(node * 2 + 1, mid + 1, hi, l, r, d);
tree[node] = tree[node * 2] + tree[node * 2 + 1];
}
long long query(int node, int lo, int hi, int l, int r) {
if (r < lo || hi < l) return 0;
if (l <= lo && hi <= r) return tree[node];
push(node, lo, hi);
int mid = (lo + hi) / 2;
return query(node * 2, lo, mid, l, r) +
query(node * 2 + 1, mid + 1, hi, l, r);
}
public:
LazySegTree(vector<int>& arr)
: n(arr.size()), tree(4 * arr.size()), lz(4 * arr.size()) {
build(arr, 1, 0, n - 1);
}
void add(int l, int r, long long d) { update(1, 0, n - 1, l, r, d); }
long long sum(int l, int r) { return query(1, 0, n - 1, l, r); }
};class LazySegTree {
constructor(arr) {
this.n = arr.length;
this.tree = Array(4 * this.n).fill(0n);
this.lazy = Array(4 * this.n).fill(0n);
this.#build(arr.map(BigInt), 1, 0, this.n - 1);
}
#build(arr, node, lo, hi) {
if (lo === hi) {
this.tree[node] = arr[lo];
return;
}
const mid = (lo + hi) >> 1;
this.#build(arr, node * 2, lo, mid);
this.#build(arr, node * 2 + 1, mid + 1, hi);
this.tree[node] = this.tree[node * 2] + this.tree[node * 2 + 1];
}
#apply(node, segLen, d) {
this.tree[node] += d * BigInt(segLen);
this.lazy[node] += d;
}
#push(node, lo, hi) {
if (this.lazy[node]) {
const mid = (lo + hi) >> 1;
this.#apply(node * 2, mid - lo + 1, this.lazy[node]);
this.#apply(node * 2 + 1, hi - mid, this.lazy[node]);
this.lazy[node] = 0n;
}
}
add(l, r, d) {
this.#update(1, 0, this.n - 1, l, r, BigInt(d));
}
#update(node, lo, hi, l, r, d) {
if (r < lo || hi < l) return;
if (l <= lo && hi <= r) {
this.#apply(node, hi - lo + 1, d);
return;
}
this.#push(node, lo, hi);
const mid = (lo + hi) >> 1;
this.#update(node * 2, lo, mid, l, r, d);
this.#update(node * 2 + 1, mid + 1, hi, l, r, d);
this.tree[node] = this.tree[node * 2] + this.tree[node * 2 + 1];
}
sum(l, r) {
return Number(this.#query(1, 0, this.n - 1, l, r));
}
#query(node, lo, hi, l, r) {
if (r < lo || hi < l) return 0n;
if (l <= lo && hi <= r) return this.tree[node];
this.#push(node, lo, hi);
const mid = (lo + hi) >> 1;
return (
this.#query(node * 2, lo, mid, l, r) +
this.#query(node * 2 + 1, mid + 1, hi, l, r)
);
}
}
apply+pushare the only new pieces — everything else is the basic tree.
What Changed from the Basic Tree?
The tag pair
Added:
apply(node, lo, hi, d): tree[node] += d*(hi-lo+1); lazy[node] += d;
push(node, lo, hi): hand tag to children, clear it_apply(...): self.tree[node] += d * seg_len; self.lazy[node] += d
_push(...): hand tag to children, clear itapply(...): tree[node] += d * segLen; lz[node] += d;
push(...): hand tag to children, clear it#apply(...): this.tree[node] += d * segLen; this.lazy[node] += d;
#push(...): hand tag to children, clear itbecause the delta applies to a whole segment at once — d × segment length for sums.
push() before descending
Every path that goes below a node (update split case and query split case) calls push first. Miss one call site and queries read stale values.
Lazy = basic tree +
apply/push+ push-before-descend discipline.
Common Mistakes
Forgetting d × segmentLength.
A tag of +10 on a 3-element segment adds 30 to its sum, not 10.
Pushing only in query but not update (or vice versa).
Both recursions descend — both must push. Audit every recursive call site.
Composing tags wrongly for non-commutative ops.
Addition composes freely (+a then +b = +(a+b)), so tags just sum. Range assign needs “later assignment wins” logic — different tag merge.
Complexity
| Operation | Time |
|---|---|
| Range add | O(log n) |
| Range sum | O(log n) |
| Space | O(n) — two arrays of 4n |
Premium Content
Unlock Lazy Propagation and all premium lessons with a subscription.
From ₹199.99/year — See plans