Lerp, Smoothstep, and Interpolation in Games
Linear Interpolation (Lerp)
Lerp takes three inputs: a start value, an end value, and a parameter t between 0 and 1. The formula is result = start + (end - start) * t. When t is 0, the result equals start. When t is 1, the result equals end. When t is 0.5, the result is exactly halfway between them. Values of t outside the 0-1 range extrapolate beyond the endpoints, which is sometimes useful but usually unintended.
The equivalent formula result = (1 - t) * start + t * end expresses the same operation as a weighted average. At t = 0, start gets 100% weight. At t = 0.25, start gets 75% and end gets 25%. At t = 1, end gets 100%. This weighted average interpretation makes lerp intuitive: t is the percentage of the way from start to end.
Lerp works on any numeric type. Lerp a single number for position, opacity, or volume. Lerp each component of a vector for 2D/3D position. Lerp each channel of a color for gradient effects. Lerp each property independently when transitioning between two states (like camera position and zoom simultaneously). The formula is identical in every case; only the data type changes.
Camera follow is the most common lerp pattern in games. Instead of snapping the camera to the player's position, lerp it: cameraPos = lerp(cameraPos, playerPos, 0.1). Each frame, the camera moves 10% of the remaining distance toward the player. When the player is far away, the camera moves fast. When the player is close, the camera moves slowly. The result is smooth, responsive follow with natural deceleration, all from a single line of code.
Health bar animation lerps the displayed health toward the actual health each frame, creating a smooth draining or filling effect instead of an instant snap. The visual feedback gives players time to register damage or healing, which is important for game feel.
Frame-Rate-Independent Lerp
The camera follow pattern lerp(current, target, 0.1) has a hidden problem: it behaves differently at different frame rates. At 60 FPS, it moves 10% of the remaining distance 60 times per second. At 30 FPS, it moves 10% only 30 times per second, resulting in noticeably slower convergence. Since web games run on diverse hardware at varying frame rates, this inconsistency is unacceptable.
The fix uses exponential decay: lerp(current, target, 1 - Math.pow(1 - speed, deltaTime * 60)), where deltaTime is the elapsed time in seconds since the last frame. This formula produces identical visual behavior regardless of frame rate. At 60 FPS, deltaTime is approximately 0.0167. At 30 FPS, deltaTime is approximately 0.033. The exponentiation adjusts the interpolation factor to compensate for the different time step.
An alternative approach avoids lerp entirely and uses explicit duration: start a timer when the transition begins, compute t as elapsed time divided by total duration, and lerp between the start and end values using that t. When elapsed time exceeds the duration, snap to the end value and stop. This approach guarantees the transition completes in exactly the specified duration regardless of frame rate, which is ideal for animations with fixed timing like menu transitions and cutscene camera moves.
Smoothstep and Smootherstep
Smoothstep applies an S-shaped curve to the t parameter before using it for interpolation. The formula is t = 3*t*t - 2*t*t*t (after clamping t to 0-1). The result starts slow, accelerates through the middle, and decelerates at the end. Visually, this produces movement that eases in and eases out, which looks more natural than the constant-speed motion of linear interpolation.
Smootherstep uses a fifth-degree polynomial: t = 6*t*t*t*t*t - 15*t*t*t*t + 10*t*t*t. The difference from smoothstep is that the first and second derivatives are both zero at the endpoints, producing even smoother acceleration and deceleration. The visual difference is subtle but noticeable in slow transitions. Smootherstep eliminates the slight "bump" at the start and end that smoothstep can produce.
To use smoothstep in practice, compute your linear t value (like elapsed time divided by duration), apply the smoothstep formula to get a curved t, then pass the curved t to your lerp function. The smoothstep happens to the t parameter, not to the lerp itself. This separation lets you swap in different curves without changing any of the interpolation logic.
Both smoothstep and smootherstep are built into GLSL shader language, where they are used for soft edges, gradient thresholds, and procedural patterns. In JavaScript game code, implement them as utility functions that you reuse across your entire project. A single smoothstep(t) function with the three-line formula covers every use case.
Inverse Lerp and Remapping
Inverse lerp does the reverse of lerp: given a value that falls between a start and end range, it returns the t parameter that would produce that value. The formula is t = (value - start) / (end - start). If start is 0 and end is 100, a value of 75 returns t = 0.75. If start is 20 and end is 80, a value of 50 returns t = 0.5.
Combining inverse lerp with regular lerp creates a remap function that maps a value from one range to another. First, inverse lerp to find where the value falls in the source range (getting t). Then, lerp using that t in the destination range. In code: function remap(value, fromMin, fromMax, toMin, toMax) { var t = (value - fromMin) / (fromMax - fromMin); return toMin + (toMax - toMin) * t; }
Remap is ubiquitous in game development. Map a health value (0-100) to a health bar width (0-400 pixels). Map a mouse x position (0-canvasWidth) to a game world x position. Map a noise value (-1 to 1) to a terrain height (0 to 200). Map an audio volume slider (0-1) to a decibel range (-60 to 0). Map a temperature value to a color gradient between blue and red. Every time you convert a value from one meaningful range to another, you are remapping.
Clamped remap prevents values outside the source range from producing values outside the destination range. Clamp t to 0-1 after the inverse lerp step: t = Math.max(0, Math.min(1, t)). Without clamping, a health value of 120 (from a bug or buff) would produce a health bar wider than its container. With clamping, it maxes out at the full width.
Easing Functions
Easing functions modify the t parameter to produce different acceleration profiles. Smoothstep is one easing function, but there are dozens of others. The most common categories are ease-in (starts slow, ends fast), ease-out (starts fast, ends slow), and ease-in-out (starts slow, fast in the middle, ends slow). Each category has multiple curve shapes: quadratic, cubic, quartic, quintic, sinusoidal, exponential, circular, elastic, bounce, and back.
Quadratic ease-in is t * t. It produces gentle acceleration. Quadratic ease-out is 1 - (1-t) * (1-t), or equivalently t * (2 - t). It produces gentle deceleration. Cubic variants use t * t * t and 1 - (1-t)^3 for stronger acceleration curves. Higher powers produce more dramatic slow starts or slow endings.
Elastic easing overshoots the target and oscillates, like a spring settling. Bounce easing simulates a ball bouncing to a rest. Back easing slightly overshoots then returns, creating a subtle "wind-up" or "settle" feel. These specialized easing functions add personality to UI animations, character reactions, and menu transitions. They should be used sparingly for emphasis, not applied to every interpolation in the game.
The Robert Penner easing functions, originally written for Flash, have been ported to every language including JavaScript. Libraries like tween.js, GSAP, and Phaser's built-in tween system include all standard easing functions with consistent naming. For a custom implementation, the formulas are one to three lines each and the entire set of 30+ easing functions fits in a small utility file.
Practical Interpolation Patterns
Timed transition: Store the start value, end value, start time, and duration. Each frame, compute t = (currentTime - startTime) / duration. Clamp t to 0-1. Apply an easing function. Lerp between start and end using the eased t. When t reaches 1, the transition is complete. This pattern handles menu slides, fade-in effects, zoom transitions, and any animation with a fixed duration.
Spring interpolation: Instead of lerp (which converges asymptotically and never truly arrives), spring systems have velocity that overshoots and oscillates around the target before settling. A simple spring uses a stiffness value (how hard it pulls toward the target) and a damping value (how quickly oscillation dies). Each frame: acceleration = (target - position) * stiffness, velocity = velocity * damping + acceleration, position += velocity. Stiffness of 0.1 and damping of 0.8 produces a gentle spring. Higher stiffness is snappier; lower damping produces more bounce.
Delayed chain: Stagger multiple interpolations by offsetting their start times. If five menu items each fade in over 300ms with a 50ms stagger, item 0 starts at t=0, item 1 at t=50ms, item 2 at t=100ms, and so on. This creates a cascading animation from a series of identical lerp operations with offset timing. The visual effect is dramatically more polished than animating all items simultaneously.
Lerp is the workhorse: start + (end - start) * t. Smoothstep makes it feel natural. Inverse lerp and remap convert between ranges. Frame-rate-independent lerp using exponential decay ensures consistency across devices. These four tools handle the vast majority of smooth transitions in game development.