skip to content

In React Native Animated, what does interpolate do, and why can the default extrapolate make a springy heart shrink below its resting size?

level: middleimportance: should knowfreq 45%

answer

  1. map input ranges to output ranges
  2. inputRange must not decrease
  3. strings and colours as outputs
  4. default extrapolate is extend
  5. clamp stops the overshoot

basics

~20 s

interpolate maps a value's inputRange onto an outputRange, piecewise and linear by default. Outside the range it extends the edge segment unless told otherwise, so a spring overshooting past 1 pushes the output beyond the last value; extrapolate: 'clamp' fixes that.

solid answer

~40 s

`value.interpolate({inputRange, outputRange})` returns a derived animated node that maps the input onto the output piecewise-linearly; `inputRange` must be monotonically non-decreasing and the two arrays must have the same length. Outputs can be numbers, colours or strings with units such as `'45deg'`, and an `easing` can shape each segment. Outside the input range, `extrapolate` decides: `'extend'` (the default) continues the edge segment's slope, `'clamp'` holds the edge output, and `'identity'` returns the input itself; `extrapolateLeft` and `extrapolateRight` set each side. That default bites with springs: map a progress value with `inputRange: [0, 0.5, 1]` to scale `[1, 1.4, 1]`, spring the progress to 1, and the overshoot to 1.1 extends the falling last segment, so the heart dips to 0.92 before settling. Adding `extrapolate: 'clamp'` pins it at 1. Interpolation works with the native driver.

code

tsx · 23 lines
tsx
import {Animated, useAnimatedValue} from 'react-native';

export function useHeartPop() {
  const progress = useAnimatedValue(0);

  const scale = progress.interpolate({
    inputRange: [0, 0.5, 1],
    outputRange: [1, 1.4, 1],
    extrapolate: 'clamp',
  });
  const rotate = progress.interpolate({
    inputRange: [0, 0.5, 1],
    outputRange: ['0deg', '-12deg', '0deg'],
    extrapolate: 'clamp',
  });

  const play = () => {
    progress.setValue(0);
    Animated.spring(progress, {toValue: 1, friction: 4, useNativeDriver: true}).start();
  };

  return {play, style: {transform: [{scale}, {rotate}]}};
}

go deeper

for a junior

Recall that interpolate maps an input range to an output range, and that outputs can be numbers, colours or degree strings.

for a middle

Explain the three extrapolate modes, the default of extend, and the rules on range order and length.

for a senior

Predict and prevent overshoot artefacts from springs and decay, and drive several properties from one progress value.

for a principal

Decide how shared motion tokens, such as a standard pop curve, are expressed so interpolation mistakes are not repeated across screens.

## What interpolate is `interpolate` creates a **derived value**: an animated node whose output is computed from another animated value. You describe the mapping with two arrays: - `inputRange` — the input values, which must be **monotonically non-decreasing** and contain at least two entries; - `outputRange` — the matching outputs, **the same length** as `inputRange`. Between two neighbouring input points the mapping is linear by default, and an `easing` function can shape it. Because the derived node is part of the animation graph, it works with `useNativeDriver: true` as long as the style it feeds is native-driveable. ## What the output can be - **Numbers**, for `scale`, `opacity`, `translateX` and so on. - **Colours**, such as `'rgb(200,200,200)'` to `'rgb(224,36,94)'`, for `backgroundColor` or `color`. - **Strings with units**, such as `'0deg'` to `'20deg'` for `rotate`. All output strings must have the same shape. One animated progress value from 0 to 1 can therefore drive the heart's scale, colour and tilt at once, each through its own `interpolate`. ## Extrapolation: what happens outside the range | Mode | Behaviour outside `inputRange` | |---|---| | `'extend'` (default) | continues the slope of the nearest edge segment | | `'clamp'` | holds the edge output value | | `'identity'` | returns the input value unchanged | `extrapolate` sets both sides; `extrapolateLeft` and `extrapolateRight` override one side each. ## Why the default surprises people with springs Springs overshoot their target. Suppose a progress value springs from 0 to 1, and the heart's scale is: - `inputRange: [0, 0.5, 1]` - `outputRange: [1, 1.4, 1]` The last segment falls from 1.4 at 0.5 to 1 at 1, a slope of −0.8 per unit. When the spring overshoots to 1.1, `'extend'` continues that line: 1.4 + (0.6 / 0.5) × (1 − 1.4) = **0.92**. The heart briefly shrinks below its resting size, and on an opacity mapping the same effect can produce values below 0 or above 1. With `extrapolate: 'clamp'`, any input above 1 maps to 1, so the heart settles cleanly. ## When extend is what you want - Mapping a scroll offset to a parallax translation that should keep moving beyond the range. - Converting units, such as degrees derived from a value that may run past its nominal range. ## Common mistakes 1. A decreasing `inputRange` such as `[1, 0]`, which fails the "monotonically non-decreasing" check; reverse the `outputRange` instead. 2. Arrays of different lengths. 3. Forgetting `clamp` on progress values driven by springs or `decay`. 4. Mixing number and string outputs in one `outputRange`. ## Interpolating from other animated nodes - An interpolation is itself an animated node, so it has an `interpolate` method too; you can map a progress value to a scale, then that scale to a shadow opacity. - Several interpolations can read the same source value, which keeps a multi-property effect in sync: the heart's scale, tilt and colour all follow one `progress`. - `Animated.add`, `Animated.multiply` and similar helpers combine values before interpolating, for example adding a base offset to a gesture delta. ## A worked design for the heart 1. Drive a single `progress` from 0 to 1 with a spring. 2. Interpolate `scale` with `[0, 0.5, 1] → [1, 1.4, 1]` and `extrapolate: 'clamp'`. 3. Interpolate `rotate` with `[0, 0.5, 1] → ['0deg', '-12deg', '0deg']`, also clamped. 4. Interpolate the colour from grey to red with `[0, 1]`; clamping keeps a spring's overshoot from producing an invalid colour. 5. Start everything with `useNativeDriver: true`, since transforms and colours are both native-driveable. The result is one spring, one value and three properties that can never drift apart.

  • In React Native Animated, how do you run an interpolation backwards when inputRange must be non-decreasing?
    Keep `inputRange` ascending and reverse the `outputRange`. For a value that goes from 1 to 0 while opacity should go from 0 to 1, write `inputRange: [0, 1]` and `outputRange: [1, 0]`. A descending `inputRange` such as `[1, 0]` fails the non-decreasing check.
  • What do extrapolateLeft and extrapolateRight add over extrapolate in React Native Animated?
    They set the behaviour on one side of the input range, overriding `extrapolate` for that side. A common case is clamping only the right side so a spring's overshoot cannot push past the final value, while the left side keeps extending for inputs below the range.

saying these in an interview costs you the question

  • interpolate clamps to the output range by default
  • inputRange can be in any order as long as outputRange matches
  • interpolate cannot be used with useNativeDriver: true
  • outputRange can only contain numbers, not colours or degrees
  • A spring's overshoot never affects interpolated values