Article
Thermodynamics · ⏱ ~10 min read · Last updated: 9 July 2026

Heat Transfer Numerics: Finite Differences and Finite Elements

Fourier's heat equation has an exact solution only for a handful of simple geometries and boundary conditions. Everything else — a heat sink with fins, a room with a window, a 2D metal plate with a hot spot — is solved by turning the continuous PDE (partial differential equation, one relating a quantity to its rates of change in space and time) into a grid of discrete unknowns and marching them forward in time. How you build that grid, and how you step through time, determines whether your simulation is fast, accurate, or simply blows up.

TL;DR: Solving the heat equation numerically means picking a grid and a time-stepping scheme. Explicit finite differences are fast per step but blow up unless the CFL stability limit is respected; implicit and Crank-Nicolson schemes trade a linear solve each step for unconditional stability; finite elements swap the grid for a triangular mesh to handle irregular shapes, using the same time-stepping.

1. The Heat Equation and Why We Discretize

Fourier's law states that heat flux is proportional to the negative temperature gradient, q = −k∇T. Combined with conservation of energy, it gives the heat diffusion equation, a parabolic PDE:

∂T/∂t = α ∇²T α = k / (ρ c_p) — thermal diffusivity (m²/s) k = thermal conductivity, ρ = density, c_p = specific heat

Analytic solutions exist for infinite rods, semi-infinite slabs, or simple Fourier-series expansions on rectangles with trivial boundary conditions. A real object — a heat sink, a CPU die, a window pane — has irregular geometry and mixed boundary conditions, so we replace the continuous field T(x,y,t) with values on a discrete grid and replace derivatives with finite differences (or, for irregular shapes, with finite element basis functions).

2. Finite Difference Method: Explicit Scheme

Discretize space into a grid with spacing Δx and time into steps Δt. The second spatial derivative becomes a three-point stencil, and the time derivative becomes a forward difference — the explicit (FTCS) scheme:

1D: T[i]^(n+1) = T[i]^n + r · (T[i+1]^n − 2T[i]^n + T[i−1]^n) r = α·Δt / Δx² (dimensionless diffusion number) 2D (5-point stencil): T[i,j]^(n+1) = T[i,j]^n + rx·(T[i+1,j] − 2T[i,j] + T[i−1,j]) + ry·(T[i,j+1] − 2T[i,j] + T[i,j−1])

Every new value depends only on old values — no linear system to solve, trivial to vectorize, and exactly what the site's own Heat Equation 2D simulation does on an 80×80 grid every animation frame. The catch is stability.

3. Stability: The CFL / Von Neumann Condition

Explicit FTCS is only conditionally stable. Von Neumann stability analysis (expanding the error as a Fourier mode and checking its amplification factor stays ≤ 1) gives a hard limit on the time step:

1D: r = α·Δt/Δx² ≤ 1/2 2D: r ≤ 1/4 (rx + ry ≤ 1/2 for equal spacing) 3D: r ≤ 1/6 Violate this and every grid point oscillates with growing amplitude — temperatures diverge to ±∞ within a handful of frames.

Halve Δx

Stable Δt shrinks by 4× (2D) since the limit scales with Δx². Refining the mesh for accuracy quietly costs 4× as many time steps to cover the same simulated duration.

Symptom

A checkerboard pattern of alternating hot/cold values that grows every frame is the signature of a blown CFL limit — not a physics bug.

Practical fix

Pick Δt = 0.9 × the stability bound (a small safety margin), or switch to an unconditionally stable implicit scheme (§4).

4. Implicit and Crank-Nicolson Schemes

The implicit (BTCS) scheme evaluates the spatial stencil at the new time level, which requires solving a linear system every step but removes the stability limit entirely:

Implicit (BTCS): −r·T[i−1]^(n+1) + (1+2r)·T[i]^(n+1) − r·T[i+1]^(n+1) = T[i]^n → tridiagonal system, solved with the Thomas algorithm in O(N) per step Crank-Nicolson (average of explicit + implicit, 2nd-order in time): T[i]^(n+1) − (r/2)(T[i+1]−2T[i]+T[i−1])^(n+1) = T[i]^n + (r/2)(T[i+1]−2T[i]+T[i−1])^n

BTCS is unconditionally stable for any Δt but only first-order accurate in time; Crank-Nicolson is second-order accurate and still unconditionally stable, at the cost of solving a tridiagonal (1D) or banded (2D, via ADI — alternating direction implicit) system each step. For 2D grids, ADI splits one full step into two half-steps, each solving a cheap tridiagonal system along one axis, which keeps the cost close to explicit while removing the CFL limit.

5. Finite Element Method: The Basic Idea

FDM needs a regular grid; the finite element method handles arbitrary geometry — an L-shaped bracket, a gear tooth, a car body — by tiling the domain with triangles (2D) or tetrahedra (3D) and representing temperature as a piecewise-linear function over the mesh, weighted by nodal values.

Weak form of the heat equation (Galerkin method): ∫Ω φ_i (∂T/∂t) dΩ = −∫Ω k ∇φ_i · ∇T dΩ + boundary terms Discretized: M (dT/dt) + K T = F M = mass matrix (from φ_i φ_j terms) K = stiffness matrix (from ∇φ_i · ∇φ_j terms, same role as the FDM stencil) F = boundary/source load vector

Once assembled, M and K are sparse matrices and the time derivative is still handled with an explicit, implicit, or Crank-Nicolson scheme exactly as in §2–4 — FEM changes how space is discretized, not how time is stepped. The payoff is that mesh density can vary locally (fine near sharp corners, coarse in open regions), something a uniform FDM grid cannot do without heavy modification.

6. Boundary Conditions in Practice

Dirichlet (fixed T)

Edge nodes are clamped to a fixed value every step — e.g. a wall held at 0 °C. Simplest to implement: overwrite after the update.

Neumann (fixed flux)

Insulated boundary: ∂T/∂n = 0, implemented with a ghost node equal to its mirror neighbour so no heat crosses the edge.

Robin (convective)

Newton cooling to ambient air: −k∂T/∂n = h(T − T_amb). Blends Dirichlet and Neumann behaviour depending on h.

Periodic

Wraps the last column back to the first — useful for simulating an infinite repeating pattern with a small grid.

7. JavaScript Implementation

// Explicit FTCS solver for the 2D heat equation with Dirichlet edges
function stepHeat2D(T, next, nx, ny, alpha, dt, dx) {
  const r = alpha * dt / (dx * dx);
  if (r > 0.24) throw new Error(`Unstable: r=${r.toFixed(3)} exceeds 0.25 (2D CFL limit)`);

  for (let j = 1; j < ny - 1; j++) {
    for (let i = 1; i < nx - 1; i++) {
      const idx = j * nx + i;
      const lap = T[idx + 1] + T[idx - 1] + T[idx + nx] + T[idx - nx] - 4 * T[idx];
      next[idx] = T[idx] + r * lap;
    }
  }
  // Dirichlet edges: hold boundary rows/cols at their existing value
  for (let i = 0; i < nx; i++) { next[i] = T[i]; next[(ny-1)*nx+i] = T[(ny-1)*nx+i]; }
  for (let j = 0; j < ny; j++) { next[j*nx] = T[j*nx]; next[j*nx+nx-1] = T[j*nx+nx-1]; }
  return next;
}

// Thomas algorithm — solves a tridiagonal system in O(N), used for 1D implicit/Crank-Nicolson
function thomasSolve(a, b, c, d) {
  const n = d.length;
  const cp = new Float64Array(n), dp = new Float64Array(n);
  cp[0] = c[0] / b[0]; dp[0] = d[0] / b[0];
  for (let i = 1; i < n; i++) {
    const m = b[i] - a[i] * cp[i-1];
    cp[i] = c[i] / m;
    dp[i] = (d[i] - a[i] * dp[i-1]) / m;
  }
  const x = new Float64Array(n);
  x[n-1] = dp[n-1];
  for (let i = n-2; i >= 0; i--) x[i] = dp[i] - cp[i] * x[i+1];
  return x;
}

8. Applications and Limits