AI Computer Institute
Expert-curated CS & AI curriculum aligned to CBSE standards. A bharath.ai initiative. About Us

Pooling, Stride, and Feature Maps Demystified

📚 Deep Learning⏱️ 19 min read🎓 Grade 11
✍️ AI Computer Institute Editorial Team Updated: September 2026 CBSE-aligned · Peer-reviewed · 19 min read
Content curated by subject matter experts with IIT/NIT backgrounds. All chapters are fact-checked against official CBSE/NCERT syllabi.

During the Assam floods of the last few monsoons, agencies working with the National Remote Sensing Centre have had to turn RISAT and Cartosat satellite passes into flood-extent maps within hours of a scene landing on the ground station, because relief routing decisions depend on it. A single synthetic-aperture-radar scene of the Brahmaputra basin can span tens of thousands of pixels on a side. A convolutional network built to segment "water" from "land" in that scene cannot run every layer at full resolution — a stack of even modest 3×3 convolutions on a 10,000×10,000 image, keeping every layer at that size, would need more memory and compute than any operational pipeline can afford, and the flood bulletin has to go out before the water does. The standard fix is architectural: shrink the spatial grid as the network goes deeper, so early layers look at fine detail over a large canvas and later layers look at coarse, abstracted detail over a small one. The two knobs that make this possible are stride and pooling, and the thing they operate on is the feature map — the actual object a convolution produces. This chapter builds all three from the numbers up.

What a feature map actually is

A convolution layer does not produce "an image." It slides a small learned kernel (say 3×3) across the input, and at every position it stops, it computes one number: the sum of the kernel's weights multiplied elementwise with the patch of input directly underneath it. Do this at every valid position and you get a 2-D grid of numbers — the feature map. Every entry in that grid answers the same question for a different location: "how strongly does the pattern this kernel has learned to detect appear here?" A network with 64 filters in a layer produces 64 such feature maps stacked into a 3-D volume (height × width × 64), each one a specialised detector — one might fire on vertical edges, another on a particular texture, another (many layers deeper) on "boat-shaped blob surrounded by water."

One precision note that matters when you trace numbers by hand: what deep learning frameworks call "convolution" is, strictly, cross-correlation — the kernel is not flipped before the multiply-and-sum, unlike the convolution operator from signal-processing theory. Every worked example below uses this convention, because it is what PyTorch's and TensorFlow's Conv2d actually compute.

Stride: the step size that controls resolution

When the kernel slides across the input, stride is how many pixels it jumps between stops. Stride 1 means it stops at every position, overlapping heavily with its previous stop. Stride 2 means it skips every other position, so the output grid comes out roughly half the width and height of the stride-1 output, and only a quarter the number of dot products get computed — a direct, controllable lever on compute cost.

For an input of width W, a kernel of width K, padding P pixels added to each side, and stride S, the output width is:

O = ⌊(W − K + 2P) / S⌋ + 1

The same formula applies independently to height. This single equation is the entire arithmetic of "how big is the next feature map" — everything else in this chapter is just applying it correctly and understanding what it's telling you.

Worked example: convolving a 5×5 flood patch, stride 1 vs stride 2

Take a tiny 5×5 crop from a binarized satellite mask, where 1 marks a water pixel and 0 marks land, with a clean vertical water/land boundary running down the middle:

I =
[1, 1, 1, 0, 0]
[1, 1, 1, 0, 0]
[1, 1, 1, 0, 0]
[0, 0, 0, 0, 0]
[0, 0, 0, 0, 0]

Convolve it with a 3×3 Prewitt-style vertical-edge kernel, which is exactly designed to fire where pixel intensity changes left-to-right:

K =
[ 1, 0, −1]
[ 1, 0, −1]
[ 1, 0, −1]

With stride 1 and no padding, the output size is ⌊(5−3)/1⌋+1 = 3, so a 3×3 feature map. Working the top-left window by hand: the patch rows 0–2, cols 0–2 is all 1s, so each row's dot product with [1,0,−1] is 1·1+1·0+1·(−1) = 0, giving a total of 0 for that position — correct, because that window sits entirely inside the water region with no edge to detect. Sliding one column right, the patch cols 1–3 now straddles the boundary (values 1,1,0 per row), and each row's dot product becomes 1·1+1·0+0·(−1) = 1, summing to 3 across three rows. Carrying this out for every position gives the complete stride-1 feature map:

Feature map (stride 1, 3×3):
[0, 3, 3]
[0, 2, 2]
[0, 1, 1]

Notice the pattern is exactly what a vertical-edge detector should produce: zero in the column of windows that never crosses the boundary, positive values wherever the window does cross it. Now redo it with stride 2. Output size is ⌊(5−3)/2⌋+1 = 2, a 2×2 map, and the four surviving positions are exactly the stride-1 map sampled at rows {0,2} and columns {0,2}:

Feature map (stride 2, 2×2):
[0, 3]
[0, 1]

Stride 2 didn't change what each surviving output value means — position (0,1) in the stride-2 map is still "3," the identical number computed at position (0,2) in the stride-1 map — it simply discarded three-quarters of the positions the kernel would otherwise have visited. This is the whole mechanical truth of stride: same convolution rule, coarser sampling grid, cheaper output.

Verify with code rather than trusting the hand trace:

import numpy as np

def conv2d(X, K, stride=1):
    H, W = X.shape
    kh, kw = K.shape
    oh = (H - kh) // stride + 1
    ow = (W - kw) // stride + 1
    out = np.zeros((oh, ow))
    for i in range(oh):
        for j in range(ow):
            si, sj = i * stride, j * stride
            patch = X[si:si + kh, sj:sj + kw]
            out[i, j] = np.sum(patch * K)
    return out

I = np.array([[1,1,1,0,0],
              [1,1,1,0,0],
              [1,1,1,0,0],
              [0,0,0,0,0],
              [0,0,0,0,0]])
K = np.array([[1,0,-1],
              [1,0,-1],
              [1,0,-1]])

print(conv2d(I, K, stride=1))
# [[0. 3. 3.]
#  [0. 2. 2.]
#  [0. 1. 1.]]

print(conv2d(I, K, stride=2))
# [[0. 3.]
#  [0. 1.]]

Pooling: throwing away exact position on purpose

Pooling is a different operation that happens to also shrink a feature map, which is exactly why it gets confused with stride (more on that below). A pooling layer slides a window over the feature map — commonly 2×2 with stride 2, so the windows don't overlap — and at each stop replaces the whole window with one summary number. Max-pooling keeps the largest activation in the window; average-pooling keeps the mean. Crucially, pooling has no learned weights at all — it is a fixed rule, not something backpropagation adjusts.

Two things pooling buys a network, beyond the same compute savings stride gives: robustness to small spatial jitter (if the pattern a filter detects shifts by a pixel but stays inside the same pooling window, the pooled output doesn't change), and a mild regularising effect from throwing away the precise position of the maximum. Neither of those is "just downsampling" — a 4×4 average-blur-then-subsample would also shrink the map, but it wouldn't preferentially preserve the strongest signal the way max-pooling does, which matters when the network's job is to answer "is this pattern present anywhere in this region," not "what does this region look like on average."

Worked example: max- and average-pooling a 4×4 feature map

Take a 4×4 feature map (post-ReLU activations from some earlier layer):

FM =
[1, 3, 2, 4]
[5, 6, 1, 2]
[0, 2, 9, 3]
[1, 0, 4, 2]

Apply 2×2 max-pooling, stride 2 (non-overlapping windows). Output size is ⌊(4−2)/2⌋+1 = 2, so 2×2. The top-left window covers rows 0–1, cols 0–1: values 1, 3, 5, 6 → max 6. Top-right (rows 0–1, cols 2–3): 2, 4, 1, 2 → max 4. Bottom-left (rows 2–3, cols 0–1): 0, 2, 1, 0 → max 2. Bottom-right (rows 2–3, cols 2–3): 9, 3, 4, 2 → max 9.

Max-pooled (2×2):
[6, 4]
[2, 9]

Average-pooled (2×2):
[3.75, 2.25]
[0.75, 4.5]

Now test the translation-invariance claim directly instead of taking it on faith. Swap the two top values inside the bottom-right window only, so the "9" moves from position (2,2) to (2,3):

FM' bottom-right block:
[3, 9]        (was [9, 3])
[4, 2]

The max of that window is still 9. The input shifted by one pixel; the pooled output for that region did not change at all. That is what "translation invariance from pooling" concretely means — and the same trace also shows its limit: the invariance only holds while the shift keeps the peak inside the same window. Shift the 9 one more column right, out of the block entirely, and the pooled value at that position drops.

def maxpool2d(X, size=2, stride=2):
    H, W = X.shape
    oh = (H - size) // stride + 1
    ow = (W - size) // stride + 1
    out = np.zeros((oh, ow))
    for i in range(oh):
        for j in range(ow):
            si, sj = i * stride, j * stride
            out[i, j] = np.max(X[si:si + size, sj:sj + size])
    return out

FM = np.array([[1,3,2,4],
               [5,6,1,2],
               [0,2,9,3],
               [1,0,4,2]])
print(maxpool2d(FM))
# [[6. 4.]
#  [2. 9.]]

The full pipeline, drawn

1. Convolution + stride: same kernel, coarser sampling grid 1 1 1 0 0 1 1 1 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 input (5×5) — binarized flood mask window A (col 0) → output 0 window B (col 2, stride 2) → output 3 kernel K (Prewitt Gx) 1 0 −1 1 0 −1 1 0 −1 vertical-edge detector stride 1 → 3×3 0 3 3 0 2 2 0 1 1 ⌊(5−3)/1⌋+1 = 3 stride 2 → 2×2 0 3 0 1 ⌊(5−3)/2⌋+1 = 2 2. Max-pooling: fixed rule, no learned weights 1 3 2 4 5 6 1 2 0 2 9 3 1 0 4 2 feature map (4×4) max, 2×2, stride 2 6 4 2 9 pooled output (2×2)

The misconception: "stride-2 convolution and 2×2 max-pooling are the same operation"

They get confused because both shrink a feature map by the identical arithmetic — a 2×2-window, stride-2 pooling layer and a 2×2-kernel, stride-2 convolution layer take a 4×4 input to the same 2×2 output size — and both use the word "stride" to describe how far the window moves. But stride is a property of any windowed sweep, not something unique to either operation, and the two are not interchangeable. A strided convolution computes, at every stop, a weighted sum using a kernel whose weights are trainable parameters updated by backpropagation; its output values are a learned function of the input, and given the right weights it could in principle imitate an averaging or even a soft-max-like pooling behaviour — but it is never restricted to that. Max-pooling has zero trainable parameters: it always keeps the largest value in the window, on every input, forever, with nothing for gradient descent to adjust. That fixed rule is precisely what gives pooling its local translation invariance (demonstrated above with the shifted "9") — a property a randomly initialised strided convolution does not have and would have to learn, if it ever gets there at all.

This is also why the distinction is a live architectural decision, not a historical footnote: architectures such as ResNet largely replaced pooling layers with stride-2 convolutions for downsampling, trading pooling's built-in, parameter-free invariance for extra learnable capacity at every downsampling step. Both approaches shrink the feature map by the same formula; they get there by fundamentally different computations, and a network's behaviour differs depending on which one you pick.

Bonus depth: stride and pooling both accelerate receptive field growth

A neuron's receptive field is the region of the original input that can influence its value. For a stack of layers, it grows recursively: if r is the receptive field size and j is the cumulative "jump" (the product of every stride seen so far, starting at j₀ = 1), then adding a layer with kernel size k and stride s updates them as r_new = r_old + (k − 1) · j_old and j_new = j_old · s. Stack two 3×3, stride-1 convolutions and the receptive field grows from 3 to 3 + (3−1)·1 = 5 — the textbook result behind VGG's design choice to prefer stacks of small kernels. But make the first layer stride 2 instead, and the jump for the second layer becomes 2, so the same second 3×3 kernel now covers 3 + (3−1)·2 = 7 pixels of the original input — a larger receptive field from an identical kernel, purely because an earlier stride widened the sampling grid. Pooling layers contribute to this jump exactly the same way a strided convolution would, since the formula only cares about window size and stride, not whether the window carries trainable weights. This is the real reason downsampling early in a network (via stride or pooling) makes every subsequent layer "see" more of the image per neuron — not a side effect, but the mechanism deep architectures rely on to reach large receptive fields without impossibly large kernels.

Active recall

Attempt each question before reading its answer.

  1. A 32×32 single-channel input is convolved with a 5×5 kernel, stride 1, no padding. What is the output size?
  2. Same input and kernel, but stride 2, no padding. What is the output size?
  3. Take the stride-1 feature map from the worked example, [[0,3,3],[0,2,2],[0,1,1]], and apply 2×2 max-pooling with stride 1 (overlapping windows this time). Give the resulting map size and values.
  4. Why do CNNs typically run several stride-1 convolution layers before the first downsampling step, rather than downsampling the raw input immediately?
  5. A 224×224 input passes through 5 downsampling stages (pooling or stride-2 convolution), each cleanly halving spatial size. What is the spatial size after all 5 stages?
  6. A max-pool layer (2×2 window, stride 2) and a convolution layer (2×2 kernel, stride 2) both turn a 4×4 feature map into a 2×2 one. Are they computing the same thing? Justify your answer.

Answers

1. O = ⌊(32−5)/1⌋+1 = 27+1 = 28, so a 28×28 feature map.

2. O = ⌊(32−5)/2⌋+1 = ⌊13.5⌋+1 = 13+1 = 14, so 14×14. (The fractional 13.5 gets floored — the kernel simply stops sliding once fewer than 5 columns remain, and the remaining pixel is left unused since no padding was added.)

3. Output size ⌊(3−2)/1⌋+1 = 2, so 2×2. Window (0,0) over rows0–1,cols0–1 = {0,3,0,2} → max 3. Window (0,1) over rows0–1,cols1–2 = {3,3,2,2} → max 3. Window (1,0) over rows1–2,cols0–1 = {0,2,0,1} → max 2. Window (1,1) over rows1–2,cols1–2 = {2,2,1,1} → max 2. Result: [[3,3],[2,2]].

4. Early stride-1 layers keep full spatial resolution while the network is still learning low-level, spatially precise patterns (edges, corners, small textures). Downsampling immediately would throw away the fine-grained detail those early kernels need before the network has had any chance to extract it. By the time downsampling happens, the relevant information has already been pushed into the channel/depth dimension as detected patterns, so losing raw pixel-level position at that point costs far less.

5. 224 / 2⁵ = 224/32 = 7, so a 7×7 spatial map — the same arithmetic behind the 7×7 feature-map size at the end of a standard ResNet-50 backbone fed 224×224 input.

6. No. Both reduce spatial size by the same formula, but max-pooling has no trainable parameters and always applies the fixed rule "keep the largest value" to every window on every input. The stride-2 convolution computes a weighted sum using a trainable 2×2 kernel whose weights are learned through backpropagation, so its output is a function of parameters the network adjusts during training, not a fixed rule — it could in principle come to approximate an averaging or max-like behaviour for specific learned weights, but it is not constrained to and starts out computing something entirely different.

Think About It

Think about this: How would you explain pooling, stride, and feature maps demystified to a friend who has never seen a computer? What real-world analogy would you use? Imagine you had to build a system using these concepts — what would be your first step? Try this: before moving on, write down three things you learned and one question you still have.

Practice Exercises

Now it is time to practice! Complete these challenges to solidify your understanding:

  • Exercise 1: Write a short program that demonstrates the core concept from this chapter. Test it with at least 3 different inputs.
  • Exercise 2: Find a real-world example where pooling, stride, and feature maps demystified is used in an Indian company (like TCS, Infosys, Flipkart, or ISRO). Write a paragraph explaining the connection.
  • Exercise 3: Create a mind-map connecting pooling, stride, and feature maps demystified to at least 3 other topics you have studied.
← Convolutional Neural Networks: From LeNet to ResNetTransfer Learning: Standing on Giants' Shoulders →

Found this useful? Share it!

📱 WhatsApp 🐦 Twitter 💼 LinkedIn