Forward Propagation

Consider a network that must decide whether an image contains a cat. It is given nothing but a grid of pixel intensities. Somewhere between the raw pixels and the final "cat" or "no cat" verdict, the network must transform the input. It does this through a sequence of intermediate representations. Each one is a step further from raw sensory data. Each is also a step closer to an abstract decision. Forward propagation is the precise mechanical process that performs this transformation. It takes an input vector and pushes it through a sequence of affine maps and nonlinearities. The result is an output. Every prediction a neural network ever makes — in vision, in speech, in language, in game-playing agents — is the result of a forward pass. Forward propagation matters because it is the computational backbone on which everything else in deep learning is built. Training requires computing a loss. The loss is defined on the output of a forward pass. Backpropagation, the algorithm used to compute gradients, is literally defined as a reversal of the forward computation graph. Inference in production systems is nothing but a forward pass. This is the thing that actually runs when you ask a voice assistant a question. It is also what runs when a self-driving car classifies a pedestrian. It is optimized for speed and executed billions of times per day. Understanding forward propagation deeply matters a great deal. It means understanding it as a sequence of matrix operations with well-defined shapes and costs. This is the prerequisite for understanding training. It is also the prerequisite for debugging shape mismatches. And it is needed for reasoning about inference latency. This article builds on your knowledge of The Multilayer Perceptron. It makes the layer-by-layer computation fully explicit. Rather than treating "the network computes an output" as a black box, we go further. We will write down the exact matrix multiplications. We will specify the exact shapes involved at each layer. We will also specify the exact order of operations. We will pay close attention to how the computation is vectorized across a batch of examples. Real systems never propagate one example at a time. Instead, they propagate thousands simultaneously to exploit modern hardware. By the end, you should be able to derive the shape and cost of every intermediate tensor in an MLP from first principles. You should be able to trace a concrete numerical example by hand. You should also see precisely how the forward pass sets up the machinery. This machinery is used later for Loss Functions for Deep Networks. It is also used for representing computation as Computational Graphs.

Key takeaways
  • Forward propagation computes a network's output by applying, layer by layer, an affine transform followed by a nonlinear activation.
  • Each layer's computation is Z[l]=W[l]A[l−1]+b[l]\mathbf{Z}^{[l]} = \mathbf{W}^{[l]}\mathbf{A}^{[l-1]} + \mathbf{b}^{[l]}, then A[l]=g[l](Z[l])\mathbf{A}^{[l]} = g^{[l]}(\mathbf{Z}^{[l]}).
  • Batching stacks examples into matrix columns (or rows), turning per-example vector ops into single matrix multiplications.
  • Time cost is dominated by matrix multiplications: roughly O(∑lnl−1nl⋅m)O(\sum_l n_{l-1} n_l \cdot m) multiply-adds for mm examples.
  • Space cost requires caching every intermediate activation, since these values are needed later by backpropagation.
  • Correctness follows by induction on layer index: each layer's output shape and semantics are guaranteed by the invariant that its input matches the previous layer's output.
The core insight

Forward propagation is repeated matrix multiplication plus elementwise nonlinearity, carrying a fixed shape invariant from layer to layer until the input becomes the output.

The idea

A feedforward neural network with LL layers is defined by weight matrices W[1],…,W[L]\mathbf{W}^{[1]}, \dots, \mathbf{W}^{[L]}, bias vectors b[1],…,b[L]\mathbf{b}^{[1]}, \dots, \mathbf{b}^{[L]}, and activation functions g[1],…,g[L]g^{[1]}, \dots, g^{[L]}. Forward propagation is the procedure that, given an input x\mathbf{x}, computes the network's output y^\hat{\mathbf{y}} by applying these layers in sequence. Concretely, define a[0]=x\mathbf{a}^{[0]} = \mathbf{x} as the input, and for each layer l=1,…,Ll = 1, \dots, L compute two quantities: the pre-activation (or logit)

z[l]=W[l]a[l−1]+b[l],\mathbf{z}^{[l]} = \mathbf{W}^{[l]} \mathbf{a}^{[l-1]} + \mathbf{b}^{[l]},

and the activation

a[l]=g[l](z[l]).\mathbf{a}^{[l]} = g^{[l]}\left(\mathbf{z}^{[l]}\right).

The final activation a[L]\mathbf{a}^{[L]} is the network's output y^\hat{\mathbf{y}}. The word "forward" refers to the direction of information flow: data moves strictly from the input layer toward the output layer, with no loops or backward edges, which is why this class of models is called a feedforward network. The key structural fact is that W[l]\mathbf{W}^{[l]} has shape nl×nl−1n_l \times n_{l-1}, where nl−1n_{l-1} is the width (number of units) of the previous layer and nln_l is the width of the current layer. This shape convention guarantees that W[l]a[l−1]\mathbf{W}^{[l]} \mathbf{a}^{[l-1]} is well-defined and produces a vector of length nln_l, which is exactly the width expected by layer ll. This is the "shape invariant" that forward propagation preserves at every step, and it is the single most useful fact for debugging real networks: any dimension mismatch in a forward pass is a violation of this invariant, almost always caused by a wrongly transposed matrix or a misconfigured layer width. In practice, we never run one input vector through the network at a time — we run a batch of mm examples simultaneously. Stacking the examples as columns of a matrix A[0]=X∈Rn0×m\mathbf{A}^{[0]} = \mathbf{X} \in \mathbb{R}^{n_0 \times m}, the exact same equations apply with vectors replaced by matrices:

Z[l]=W[l]A[l−1]+b[l],A[l]=g[l](Z[l]),\mathbf{Z}^{[l]} = \mathbf{W}^{[l]} \mathbf{A}^{[l-1]} + \mathbf{b}^{[l]}, \qquad \mathbf{A}^{[l]} = g^{[l]}(\mathbf{Z}^{[l]}),

where the addition of b[l]∈Rnl\mathbf{b}^{[l]} \in \mathbb{R}^{n_l} to a matrix of shape nl×mn_l \times m is broadcast — the bias vector is added to every column. This batched form is what actually runs on GPUs: a handful of large matrix multiplications rather than millions of small vector operations, which is essential for computational efficiency (discussed in detail below).

How it works

Forward propagation proceeds as a simple loop over layers, each iteration performing one matrix multiply, one broadcasted bias addition, and one elementwise nonlinearity. The only subtlety is bookkeeping: every intermediate Z[l]\mathbf{Z}^{[l]} and A[l]\mathbf{A}^{[l]} must typically be cached, because although forward propagation alone does not need them after use, the backpropagation algorithm that computes gradients will need them.

Step-by-step mechanism: 1. Load the input batch X\mathbf{X} into A[0]\mathbf{A}^{[0]}. 2. For each layer ll from 11 to LL: a. Compute the pre-activation Z[l]=W[l]A[l−1]+b[l]\mathbf{Z}^{[l]} = \mathbf{W}^{[l]} \mathbf{A}^{[l-1]} + \mathbf{b}^{[l]}. b. Apply the nonlinearity elementwise: A[l]=g[l](Z[l])\mathbf{A}^{[l]} = g^{[l]}(\mathbf{Z}^{[l]}). c. Cache Z[l]\mathbf{Z}^{[l]} and A[l]\mathbf{A}^{[l]} for later use (e.g. by backpropagation). 3. Return A[L]\mathbf{A}^{[L]} as the prediction Y^\hat{\mathbf{Y}}. The choice of g[l]g^{[l]} varies by layer. Hidden layers commonly use ReLU, g(z)=max⁡(0,z)g(z) = \max(0, z), or tanh; the output layer's activation is chosen to match the task — sigmoid for binary classification, softmax for multiclass classification, or the identity function for regression. This choice does not change the mechanics of forward propagation, only the specific function applied in step 2b. ``` function FORWARD_PROPAGATE(X, weights[1..L], biases[1..L], activations[1..L]):

# X has shape (n0, m): n0 input features, m examples in the batch A[0] = X cache = {} # will store Z[l], A[l] for every layer for l = 1 to L: W = weights[l] # shape (n_l, n_{l-1}) b = biases[l] # shape (n_l, 1), broadcast over m columns g = activations[l] Z[l] = MATMUL(W, A[l-1]) + b # shape (n_l, m) A[l] = ELEMENTWISE(g, Z[l]) # shape (n_l, m), same shape as Z[l] cache[l] = (Z[l], A[l-1]) # store what backprop will need

# (A[l] itself is available as A[l-1] of the next iteration,

# and also cached explicitly if this is the last layer) Yhat = A[L] return Yhat, cache ``` Two invariants make this loop correct by construction. First, the shape invariant: at the start of iteration ll, A[l−1]\mathbf{A}^{[l-1]} has shape nl−1×mn_{l-1} \times m by the induction hypothesis (true at l=1l=1 since A[0]=X\mathbf{A}^{[0]} = \mathbf{X} has shape n0×mn_0 \times m by definition), and since W[l]\mathbf{W}^{[l]} has shape nl×nl−1n_l \times n_{l-1}, the product has shape nl×mn_l \times m, matching what the next iteration expects. Second, the semantic invariant: A[l]\mathbf{A}^{[l]} always represents "the representation of the input batch as seen after ll layers of processing," so that composing LL layers computes exactly the function the architecture defines, namely f(x)=g[L](W[L]g[L−1](⋯g[1](W[1]x+b[1])⋯ )+b[L])f(\mathbf{x}) = g^{[L]}(\mathbf{W}^{[L]} g^{[L-1]}(\cdots g^{[1]}(\mathbf{W}^{[1]}\mathbf{x} + \mathbf{b}^{[1]}) \cdots) + \mathbf{b}^{[L]}).

Figure
Forward propagation through a 3-layer MLP, shown as a layered computational pipeline with explicit tensor shapes
X (n0, m) W1·A0+b1 (n1, m)=Z1 A1=g1(Z1) (n1, m) W2·A1+b2 (n2, m)=Z2 A2=g2(Z2) (n2, m) W3·A2+b3 → g3 (n3, m)=Ŷ Cache kept for backprop: Z1, A1, Z2, A2, Z3, A3 (all six tensors) n0=input width, n1,n2=hidden widths, n3=output width, m=batch size Data flows strictly left→right: no arrow ever points backward.

Complexity & correctness

Time complexity. The dominant cost at layer ll is the matrix multiplication W[l]A[l−1]\mathbf{W}^{[l]} \mathbf{A}^{[l-1]}, where W[l]∈Rnl×nl−1\mathbf{W}^{[l]} \in \mathbb{R}^{n_l \times n_{l-1}} and A[l−1]∈Rnl−1×m\mathbf{A}^{[l-1]} \in \mathbb{R}^{n_{l-1} \times m}. A standard (non-strassen) matrix multiply of an nl×nl−1n_l \times n_{l-1} matrix by an nl−1×mn_{l-1} \times m matrix costs O(nl⋅nl−1⋅m)O(n_l \cdot n_{l-1} \cdot m) multiply-add operations, since each of the nl×mn_l \times m output entries is an inner product of length nl−1n_{l-1}. The bias addition and elementwise activation cost only O(nl⋅m)O(n_l \cdot m), which is dominated by the matrix multiply whenever nl−1>1n_{l-1} > 1. Summing over all LL layers, the total time for one forward pass over a batch of mm examples is

O ⁣(m∑l=1Lnl−1nl).O\!\left(m \sum_{l=1}^{L} n_{l-1} n_l\right).

This is tight (not just an upper bound) for standard dense matrix multiplication, since every entry of every weight matrix must be read and multiplied by some activation at least once — there is no way to compute a general matrix-vector product faster than examining every matrix entry, in the absence of special structure (sparsity, low rank, etc.). If all hidden layers have roughly the same width nn, this simplifies to O(Ln2m)O(L n^2 m).

Space complexity. Two components matter. First, storing the parameters themselves costs O ⁣(∑lnl−1nl)O\!\left(\sum_l n_{l-1} n_l\right), independent of the batch size — this is fixed at training/deployment time. Second, storing the activations for a forward pass costs O ⁣(m∑lnl)O\!\left(m \sum_l n_l\right), since each layer's output A[l]\mathbf{A}^{[l]} has nl×mn_l \times m entries. If the goal is inference only, one can discard A[l−1]\mathbf{A}^{[l-1]} as soon as A[l]\mathbf{A}^{[l]} is computed, needing only O(mmax⁡lnl)O(m \max_l n_l) memory at any instant (two buffers). However, if a backward pass will follow (training), every Z[l]\mathbf{Z}^{[l]} and A[l]\mathbf{A}^{[l]} must be retained, since the chain rule needs them — this is precisely why training consumes far more memory than inference, and it is the reason techniques like gradient checkpointing exist (trading recomputation for memory).

Correctness. The forward pass computes exactly the function specified by the network architecture, and this can be shown by induction on the layer index ll. The base case is trivial: A[0]=X\mathbf{A}^{[0]} = \mathbf{X} is the input by definition, matching the architecture's specification with zero layers applied. For the inductive step, assume A[l−1]\mathbf{A}^{[l-1]} correctly holds f1:l−1(X)f_{1:l-1}(\mathbf{X}), the composition of the first l−1l-1 layers applied to the input. Then by the algorithm's definition,

A[l]=g[l](W[l]A[l−1]+b[l])=g[l](W[l]f1:l−1(X)+b[l])=f1:l(X),\mathbf{A}^{[l]} = g^{[l]}\left(\mathbf{W}^{[l]} \mathbf{A}^{[l-1]} + \mathbf{b}^{[l]}\right) = g^{[l]}\left(\mathbf{W}^{[l]} f_{1:l-1}(\mathbf{X}) + \mathbf{b}^{[l]}\right) = f_{1:l}(\mathbf{X}),

which is exactly the definition of applying ll layers. By induction this holds for all ll up to LL, so A[L]\mathbf{A}^{[L]} is the network's full function applied to X\mathbf{X}, as required. Correctness of the shapes follows by the same induction using the shape invariant argued in the previous section.

Tradeoffs. Batching increases throughput (better hardware utilization, amortized memory-access overhead) but increases peak memory use linearly in mm; very large batches can exceed device memory, forcing gradient accumulation or smaller batches at the cost of noisier gradient estimates during training. Caching every activation is essential for training but wasteful for inference-only deployment, where "forward-only" implementations deliberately drop this cache to save memory. Numerically, computing Z[l]\mathbf{Z}^{[l]} and applying g[l]g^{[l]} separately (rather than some fused approximation) is exact but two full passes over the data; production systems often fuse the matmul, bias-add, and activation into a single kernel purely for speed, without changing the mathematical result.

Worked example

Consider a tiny network with n0=2n_0 = 2 input features, one hidden layer of width n1=2n_1 = 2 with ReLU activation, and a scalar output layer n2=1n_2 = 1 with sigmoid activation (binary classification). Take a batch of m=2m = 2 examples:

X=[1−120],W[1]=[10−11],b[1]=[01],W[2]=[1−1],b[2]=0.\mathbf{X} = \begin{bmatrix} 1 & -1 \\ 2 & 0 \end{bmatrix}, \qquad \mathbf{W}^{[1]} = \begin{bmatrix} 1 & 0 \\ -1 & 1 \end{bmatrix}, \quad \mathbf{b}^{[1]} = \begin{bmatrix} 0 \\ 1 \end{bmatrix}, \qquad \mathbf{W}^{[2]} = \begin{bmatrix} 1 & -1 \end{bmatrix}, \quad b^{[2]} = 0.

Here column jj of X\mathbf{X} is example jj: example 1 is (1,2)(1, 2), example 2 is (−1,0)(-1, 0).

Layer 1 pre-activation:

Z[1]=W[1]X+b[1]=[10−11][1−120]+[01]=[1−111]+[01]=[1−122].\mathbf{Z}^{[1]} = \mathbf{W}^{[1]}\mathbf{X} + \mathbf{b}^{[1]} = \begin{bmatrix} 1 & 0 \\ -1 & 1 \end{bmatrix}\begin{bmatrix} 1 & -1 \\ 2 & 0 \end{bmatrix} + \begin{bmatrix} 0 \\ 1 \end{bmatrix} = \begin{bmatrix} 1 & -1 \\ 1 & 1 \end{bmatrix} + \begin{bmatrix} 0 \\ 1 \end{bmatrix} = \begin{bmatrix} 1 & -1 \\ 2 & 2 \end{bmatrix}.

Layer 1 activation (ReLU, elementwise):

A[1]=max⁡(0,Z[1])=[1022].\mathbf{A}^{[1]} = \max(0, \mathbf{Z}^{[1]}) = \begin{bmatrix} 1 & 0 \\ 2 & 2 \end{bmatrix}.

Layer 2 pre-activation:

Z[2]=W[2]A[1]+b[2]=[1−1][1022]+0=[1−20−2]=[−1−2].\mathbf{Z}^{[2]} = \mathbf{W}^{[2]}\mathbf{A}^{[1]} + b^{[2]} = \begin{bmatrix} 1 & -1 \end{bmatrix}\begin{bmatrix} 1 & 0 \\ 2 & 2 \end{bmatrix} + 0 = \begin{bmatrix} 1-2 & 0-2 \end{bmatrix} = \begin{bmatrix} -1 & -2 \end{bmatrix}.

Layer 2 activation (sigmoid):

Y^=σ(Z[2])=[11+e1, 11+e2]≈[0.26890.1192].\hat{\mathbf{Y}} = \sigma(\mathbf{Z}^{[2]}) = \left[ \frac{1}{1+e^{1}}, \ \frac{1}{1+e^{2}} \right] \approx \begin{bmatrix} 0.2689 & 0.1192 \end{bmatrix}.

The table below traces the shapes at every step, confirming the shape invariant argued earlier:

StepExpressionShapeValue
InputA[0]=X\mathbf{A}^{[0]} = \mathbf{X}(2,2)(2,2)[1−120]\begin{bmatrix}1&-1\\2&0\end{bmatrix}
Layer 1 pre-actZ[1]\mathbf{Z}^{[1]}(2,2)(2,2)[1−122]\begin{bmatrix}1&-1\\2&2\end{bmatrix}
Layer 1 actA[1]=ReLU(Z[1])\mathbf{A}^{[1]} = \text{ReLU}(\mathbf{Z}^{[1]})(2,2)(2,2)[1022]\begin{bmatrix}1&0\\2&2\end{bmatrix}
Layer 2 pre-actZ[2]\mathbf{Z}^{[2]}(1,2)(1,2)[−1,−2][-1,-2]
OutputY^=σ(Z[2])\hat{\mathbf{Y}} = \sigma(\mathbf{Z}^{[2]})(1,2)(1,2)[0.2689, 0.1192][0.2689,\ 0.1192]

Example 1 (features (1,2)(1,2)) is predicted "positive class" with probability ≈0.269\approx 0.269, and example 2 (features (−1,0)(-1,0)) with probability ≈0.119\approx 0.119; both are cached along with Z[1],A[1],Z[2]\mathbf{Z}^{[1]}, \mathbf{A}^{[1]}, \mathbf{Z}^{[2]} for the backward pass that would compute gradients against a loss such as binary cross-entropy — the topic of Loss Functions for Deep Networks.

Common pitfalls & traps

PitfallWhy it happensFix
Shape mismatch in matmulW[l]\mathbf{W}^{[l]} built with shape (nl−1,nl)(n_{l-1}, n_l) instead of (nl,nl−1)(n_l, n_{l-1})Fix the convention once and check every layer's expected input/output width
Forgetting to broadcast the biasTreating b[l]\mathbf{b}^{[l]} as needing per-example duplication manuallyRely on broadcasting semantics; verify shape is (nl,1)(n_l, 1), not (nl,m)(n_l, m)
Applying activation before the affine mapSwapping steps 2a and 2b in the loopAlways compute Z\mathbf{Z} first, then g(Z)g(\mathbf{Z})
Not caching intermediate valuesOnly computing the final output when training will also be neededCache every Z[l],A[l]\mathbf{Z}^{[l]}, \mathbf{A}^{[l]} if a backward pass is planned
Numerical overflow in sigmoid/softmaxComputing eze^{z} directly for large ∣z∣|z|Use numerically stable formulations (e.g. subtracting the max before exponentiating)
Using training-time layers unchanged at inferenceDropout or batchnorm behave differently forward vs. eval modeExplicitly set an inference/eval mode that changes those layers' forward behavior

Pioneers & history

The forward pass as we use it today is the computational realization of ideas that trace back to the earliest formal models of neurons and to the linear-algebraic view of computation that became standard with the rise of vectorized numerical computing.

Origins · 1 minWarren McCulloch and how this was discovered
WM
Warren McCulloch
1898–1969 · Co-formulated the first mathematical model of a neuron as a threshold logic unit, the conceptual ancestor of the layer computation used in forward propagation

FR
Frank Rosenblatt
1928–1971 · Built the Perceptron, the first trainable single-layer network, whose forward computation is a special case of the layered forward pass described here

History
1943
McCulloch and Pitts propose a formal model of a neuron as a binary threshold unit, giving the earliest precursor to the affine-plus-nonlinearity computation.
1958
Rosenblatt builds the Perceptron, implementing a single-layer forward computation in hardware and software.
1969
Minsky and Papert's analysis of the Perceptron's limitations spurs interest in multilayer architectures capable of nonlinear decision boundaries.
1986
Rumelhart, Hinton, and Williams popularize backpropagation, which requires the forward pass to cache intermediate activations for later use.
2010
s: GPU-accelerated deep learning frameworks vectorize forward propagation as batched matrix multiplications, making today's large-scale networks computationally feasible.

Where this matters

Every neural network deployed in production — image classifiers, speech recognizers, recommendation systems, large language models generating the next token — executes a forward pass to produce its output; training additionally requires a forward pass before every backward pass to compute gradients. Deep learning frameworks such as PyTorch and TensorFlow implement forward propagation as a graph of tensor operations, which is precisely the notion of a computational graph that later supports automatic differentiation. Hardware accelerators (GPUs, TPUs) are designed around the exact operation forward propagation demands most: dense matrix multiplication at scale, which is why matrix-multiply throughput (measured in FLOPs) is the headline specification of modern AI chips. Inference-serving systems (the software that answers a live user's request) optimize forward propagation specifically — through quantization, kernel fusion, and batching strategies — since inference-time forward passes, run at massive scale, dominate the operational cost of deployed AI products. Understanding forward propagation precisely is also the direct prerequisite for understanding Loss Functions for Deep Networks, since a loss is always computed as a function of the forward pass's output, and for understanding backpropagation, which is defined as a reverse traversal of the same computational graph.

:::quiz[If layer ll has nln_l units and layer l−1l-1 has nl−1n_{l-1} units, what is the shape of W[l]\mathbf{W}^{[l]}?]

  • nl−1×nln_{l-1} \times n_l
  • nl×nl−1n_l \times n_{l-1} ✓
  • nl×nln_l \times n_l
  • m×nlm \times n_l

:::quiz[Why is a bias vector b[l]∈Rnl\mathbf{b}^{[l]} \in \mathbb{R}^{n_l} added correctly to Z[l]∈Rnl×m\mathbf{Z}^{[l]} \in \mathbb{R}^{n_l \times m}?]

  • Because matrix addition requires identical shapes, so no adjustment is needed
  • Because the bias is broadcast across the mm columns, adding the same vector to every example ✓
  • Because the bias is transposed before addition
  • Because mm must always equal nln_l

:::quiz[Why must Z[l]\mathbf{Z}^{[l]} and A[l]\mathbf{A}^{[l]} typically be cached during a forward pass used for training?]

  • They are needed to compute the loss directly, and nothing else uses them
  • The backward pass (gradient computation) reuses these exact values via the chain rule ✓
  • Caching improves numerical precision of the forward computation
  • Only A[L]\mathbf{A}^{[L]}, the final output, is ever needed later

Ask the tutor: How does the cached forward-pass data get reused, step by step, when backpropagation computes gradients?

What is the difference between forward propagation and backpropagation?

Forward propagation computes the network's output given an input, moving strictly from the input layer toward the output layer through affine transforms and nonlinearities. Backpropagation is a separate algorithm, run afterward, that computes gradients of a loss with respect to every parameter by applying the chain rule in reverse order through the same layers. Forward propagation must run first because backpropagation reuses the cached intermediate values (Z[l]\mathbf{Z}^{[l]}, A[l]\mathbf{A}^{[l]}) that only a forward pass produces. In short: forward propagation answers "what does the network predict?" while backpropagation answers "how should the parameters change to reduce the loss?" — the two together form one full training step.

Why do we batch examples into a matrix instead of looping over them one at a time?

Looping over examples individually performs many small matrix-vector multiplications, which underutilizes modern hardware (GPUs, TPUs) that are optimized for large matrix-matrix multiplications with high arithmetic intensity. Stacking mm examples as columns turns the per-example computation into a single matrix multiply W[l]A[l−1]\mathbf{W}^{[l]}\mathbf{A}^{[l-1]}, amortizing memory-access overhead and exploiting parallel hardware far more efficiently. The total number of arithmetic operations is essentially the same either way — batching does not reduce asymptotic FLOPs — but it dramatically improves wall-clock throughput because of how hardware executes matrix multiplication in parallel.

Does the choice of activation function change the complexity of forward propagation?

Not asymptotically. Applying an elementwise nonlinearity to a matrix of shape nl×mn_l \times m costs O(nlm)O(n_l m) regardless of which function is used (ReLU, sigmoid, tanh, GELU, etc.), and this is always dominated by the O(nl−1nlm)O(n_{l-1} n_l m) cost of the preceding matrix multiplication whenever nl−1>1n_{l-1} > 1. Different activations do have different constant-factor costs in practice — sigmoid and tanh require evaluating exponentials, which are slower per-element than ReLU's simple comparison — but this affects wall-clock time by a constant factor, not the big-O complexity of the forward pass.

What happens differently at inference time compared to during training, for the same forward pass code?

During training, every intermediate activation must be cached because backpropagation needs it, and certain layers (dropout, batch normalization) behave stochastically or use batch statistics. At inference time, none of the cached values are needed afterward, so memory can be minimized by discarding each layer's input as soon as its output is computed. Layers like dropout are typically disabled entirely at inference (equivalent to keeping all units), and batch normalization switches from batch statistics to fixed running statistics estimated during training. The underlying matrix-multiply-plus-activation computation is identical; what changes is auxiliary layer behavior and memory management policy.

Can forward propagation be computed on any acyclic network, or only strictly layered MLPs?

The layer-by-layer description here is a special case of the more general rule: forward propagation is well-defined on any directed acyclic computational graph, since acyclicity guarantees a valid topological order in which every node's inputs are computed before the node itself. Strictly layered MLPs are the simplest such graph, a chain, but architectures with skip connections (residual networks), branching, or merging (as in many convolutional architectures) are also DAGs and are handled by the same principle: compute nodes in topological order, caching outputs as needed. This generalization is exactly the perspective formalized by Computational Graphs.

How does forward propagation relate to what I already know about the multilayer perceptron?

The Multilayer Perceptron defines the architecture — how many layers, how wide each layer is, which activation function each layer uses — as a static specification of a parameterized function. Forward propagation is the algorithm that evaluates this specification on actual data, turning the abstract definition into concrete numbers by executing the matrix multiplications and nonlinearities in order. Every MLP you have studied conceptually is executed, in practice, exactly via the forward propagation procedure described in this article.

What is the single most common bug when implementing forward propagation from scratch?

By far the most common bug is a shape mismatch or an unintended broadcast, typically from transposing a weight or bias matrix incorrectly. Because many numerical libraries broadcast shapes automatically rather than raising errors, a wrongly shaped bias or weight can produce code that runs successfully but computes a mathematically wrong function — for example, mixing information across examples in a batch instead of treating them independently. The reliable defense is to explicitly assert the expected shape of every tensor immediately after it is computed, rather than trusting that the absence of a runtime error implies correctness.

Before this

Where this leads