An animation driven entirely from JavaScript — updating a value on every frame and pushing it across the bridge to the native side to redraw — is racing against everything else the JS thread is doing that frame: state updates, event handlers, list rendering. The moment the JS thread does real work mid-animation, the animation stutters, because there was never a guarantee it would get to run before the next frame was due. This is the exact problem Reanimated exists to solve: it lets animation logic run as a worklet, a small function that executes directly on the UI thread, independent of whatever the JS thread is doing, so a gesture-driven drag or a spring animation keeps hitting its frame deadline even while JavaScript is busy elsewhere.
Reanimated is not simply "a faster Animated API" — it is a fundamentally different execution model, and the model is the part that has to be understood correctly, because misunderstanding it produces animations that appear to work in simple cases and then fail confusingly the moment a worklet tries to touch something it should not. A shared value updated on the UI thread that a component naively tries to read as a plain JavaScript number, or a worklet that calls an ordinary JavaScript function it was never told is safe to call across the thread boundary, are the two most common ways this goes wrong.
This is an exercise lesson: every code block below is something to actually run and modify, not just read. Build the pieces in order — a shared value driving a style, a worklet responding to a gesture, and a spring-based transition — and by the end you should be able to reason about which thread any given line of animation code actually executes on, which is the single most useful diagnostic question in Reanimated debugging.