Random walks
A random walk is the simplest model of something that moves by chance. At every tick of the clock it takes a single step — +1 (right) with probability $p$, or −1 (left) otherwise — and we record where it is.
Tiny rule, surprisingly rich behaviour: random walks describe a diffusing molecule, a share price, a foraging animal, the error in a noisy measurement. In this lesson you'll watch one walk wander, then run thousands at once and see a smooth bell curve appear out of pure randomness.
Everything below the sliders runs in your browser as WebAssembly — no Julia server, no install. The randomness is a small hand-written generator (a linear congruential generator), so it compiles to plain integer arithmetic and every seed reproduces exactly the same walk.
begin
using PlutoUI, WasmMakie
endOne walk, step by step
Move the sliders and the path below redraws instantly. steps is how long the clock runs, p tilts the coin toward the right, and seed picks which particular random walk you see.
steps =
p (probability of stepping right) =
seed =
path = let
# one walk, computed inline (the Float `prob` is used directly in this cell —
# threading it through a helper miscompiles under WasmTarget today)
s = (seed * 2654435761 + 12345) % 2147483647 # an LCG state in [1, 2^31-1)
if s == 0
s = 1
end
xs = Vector{Float64}(undef, nsteps + 1)
pos = 0.0
xs[1] = pos
for i in 1:nsteps
s = (s * 16807) % 2147483647 # advance the Park-Miller generator
u = Float64(s) / 2147483647.0 # a fraction in (0, 1)
pos += u < prob ? 1.0 : -1.0
xs[i + 1] = pos
end
xs
end;let
# the step index 0, 1, …, nsteps on the x-axis (integer loop — StepRangeLen
# iteration is not wasm-compilable yet)
steps_axis = Vector{Float64}(undef, nsteps + 1)
for i in 0:nsteps
steps_axis[i + 1] = Float64(i)
end
fig = Figure(size = (560, 320))
ax = Axis(fig[1, 1])
lines!(ax, [0.0, Float64(nsteps)], [0.0, 0.0]) # the starting height (0)
lines!(ax, steps_axis, path) # the walk itself
fig
endAfter 120 steps this walk ended at position 0.0. Slide seed to meet a different walk, or p to bias the coin — at p = 1 it marches straight up, at p = 0 straight down, and at p = 0.5 it drifts aimlessly.
Many walks → the bell curve
One walk is unpredictable. But run many fair walks (an even coin, p = 0.5) and a clear pattern appears: the final positions pile up into a bell-shaped curve (a normal distribution) centred on 0. This is diffusion — the same mathematics behind why a drop of ink spreads evenly through water.
A key fact: the typical distance from the centre grows like √steps, not like steps — randomness spreads slowly. Drag walks up and watch the histogram fill in. (The one-walk sliders above still bias a single walk with p.)
number of walks =
let
# Histogram of the final positions, drawn as vertical bars. We run all the walks
# in ONE flat loop (a single RNG stream chopped into `nwalks` chunks of `nsteps`)
# so the Float `prob` is only ever used at single-loop depth — the bonded value
# currently miscompiles inside a doubly-nested loop under WasmTarget.
lo = -Float64(nsteps)
hi = Float64(nsteps)
nbins = 41
counts = Vector{Float64}(undef, nbins)
for k in 1:nbins
counts[k] = 0.0
end
s = 777
pos = 0.0
j = 0
grand = nwalks * nsteps
for t in 1:grand
s = (s * 16807) % 2147483647
u = Float64(s) / 2147483647.0
pos += u < 0.5 ? 1.0 : -1.0 # fair coin (the standard diffusion demo)
j += 1
if j == nsteps # one walk just finished — record where it landed
tt = (pos - lo) / (hi - lo)
b = Int64(floor(tt * (nbins - 1))) + 1
if b < 1
b = 1
end
if b > nbins
b = nbins
end
counts[b] += 1.0
pos = 0.0
j = 0
end
end
fig = Figure(size = (560, 320))
ax = Axis(fig[1, 1])
for k in 1:nbins
center = lo + (hi - lo) * (k - 1) / (nbins - 1)
lines!(ax, [center, center], [0.0, counts[k]]) # one bar
end
fig
endrw_stats = let
# mean and spread of the final positions, accumulated in ONE flat pass. Returns a
# tuple so the markdown cell below can interpolate it: computing the numbers in a
# SEPARATE bond-dependent cell is what makes the live readout track the sliders
# (values buried inside a markdown cell's own `let` get baked to slider defaults).
m_total = 0.0
sq_total = 0.0
s = 777
pos = 0.0
j = 0
grand = nwalks * nsteps
for t in 1:grand
s = (s * 16807) % 2147483647
u = Float64(s) / 2147483647.0
pos += u < 0.5 ? 1.0 : -1.0 # fair coin (the standard diffusion demo)
j += 1
if j == nsteps
m_total += pos
sq_total += pos * pos
pos = 0.0
j = 0
end
end
m = m_total / nwalks
var = sq_total / nwalks - m * m
if var < 0.0
var = 0.0
end
sd = sqrt(var)
expected = sqrt(Float64(nsteps))
(floor(m * 100.0) / 100.0, floor(sd * 100.0) / 100.0, floor(expected * 100.0) / 100.0)
end;Across 600 walks: mean final position is about 0.27, spread (standard deviation) is about 10.76.
For a fair walk (p = 0.5) the mean sits near 0 and the spread tracks sqrt(steps) = 10.95 – that's diffusion in one number.
Appendix
The original MIT lecture builds random walks with Julia's rand and the Distributions.jl / Plots.jl stack. WebAssembly can't run that machinery in the browser, so here the randomness is a hand-written linear congruential generator (pure integer arithmetic) and the picture is drawn with WasmMakie. The mathematics — Bernoulli steps, the √n spread, the emergent bell curve — is exactly the same.