Linked List Visualizer — Free HTML CSS JS Snippet

Linked List Visualizer · Animations · Plain HTML, CSS & JS · Live preview

Share & Support

What's included

Features

Stable per-node ids (genId()) let the DOM track a specific logical node across array splices and re-renders
Delete animates the specific target node out (await-based) before the array mutates and the list re-renders
Insert at head, tail, or an arbitrary clamped index, each with a status message narrating the conceptual pointer change
CSS-drawn directional arrows (border-triangle arrowheads) connect each node to the next, fading in on entrance
Traverse walks the chain node-by-node, highlighting the active node and lighting the outgoing arrow in sequence
Explicit teaching note distinguishing true O(1) linked-list head insertion from the array-based render model's cost
Null terminator tag always rendered at the tail, reinforcing that a singly linked list ends in a null reference
All animations built with CSS opacity/transform transitions plus await-based sequencing, no animation library

About this UI Snippet

Linked List Visualizer — Animated Singly Linked List with Pointer-Rewiring Arrows, Traversal & Insert/Delete at Any Index in Vanilla JS

Screenshot of the Linked List Visualizer snippet rendered live

Most linked-list diagrams are static images because animating a real linked list is genuinely fiddly: nodes need to visually connect to whichever node comes next, and that "next" relationship changes shape on every insert and delete. This snippet renders a singly linked list as connected node boxes with CSS arrows between them, and provides insert-at-head, insert-at-tail, insert-at-index, delete-by-value, and a step-by-step traversal that highlights the walk from node to node exactly the way .next pointer-following works in a real implementation.

Data model: an array standing in for pointer-linked nodes

Under the hood, list is a plain JavaScript array of {id, value} objects — deliberately not a class-based Node with a literal .next reference, because array order already encodes the same sequential relationship a linked list's pointers encode, and it makes rendering dramatically simpler. Each node also gets a stable, monotonically increasing id from genId() so the DOM element for a given logical node can be tracked across re-renders even as its position in the array shifts. This id is the detail that makes the removal animation possible: without a stable identity, there would be no way to say "fade out *this specific* node" as opposed to "redraw everything and one fewer box happens to appear."

Why this is fundamentally different from re-rendering the whole list

The naive way to "animate" a list change is to clear the container and rebuild every node from scratch on every operation — which is what most simple to-do-list or table UIs do, and it works fine when nothing needs to visually connect to anything else. A linked list is different: the meaningful visual event isn't "a box appeared," it's "the arrow that used to point from node 2 to node 3 now points from node 2 to node 4, because node 3 was removed." This snippet's deleteValue() function demonstrates the distinction directly: before touching the underlying array, it finds the specific .node-wrap DOM element matching the node being removed, adds a .removing class that triggers a CSS opacity/scale transition, *waits* for that transition via await wait(220), and only then splices the array and calls render(). The old arrow fades with its node; the new, shorter arrow chain fades back in during the next render's entrance animation. That sequencing — animate out, then mutate state, then animate in — is the "animated pointer-rewiring" this component is built around, and it's the same principle you'd apply with FLIP animations or React's AnimatePresence in a framework context.

Insert-at-head is O(1); an array's unshift() is O(n)

Clicking Insert Head calls list.unshift({id, value}). In a *real* linked list backed by actual node objects with .next pointers, prepending a node is O(1): you allocate one new node, point its .next at the current head, and repoint the list's head reference at the new node — no other node is touched or moved. This snippet's underlying representation is a JS array for rendering convenience, and Array.prototype.unshift is actually O(n) internally (every existing element's index shifts up by one) — worth calling out explicitly as the one place this visualization's implementation convenience diverges from true linked-list performance. The about text and this snippet's insert-head status message intentionally teach the *real* linked-list complexity characteristic (O(1) head insertion because only one pointer changes) even though the underlying render array pays a different, JS-engine-specific cost; if you were building a production linked-list data structure rather than a teaching visualization, you'd use actual {value, next} node objects to get the true O(1) behavior.

Insert-at-index and the pointer-rewiring narrative

insertAt() clamps the requested index into range and calls list.splice(idx, 0, node). Conceptually, in pointer terms, this is: walk from the head idx steps to find the node right before the insertion point, create the new node with its .next set to what used to be "the node after," then repoint the previous node's .next at the new node. Only two pointers change no matter how long the list is — everything after the insertion point keeps pointing at whatever it already pointed at. The status message after every insert/delete spells out which pointer conceptually changed, reinforcing that a linked list mutation is a small, local, constant-size edit to the chain rather than a bulk rewrite.

Traversal: highlighting the walk one .next hop at a time

traverse() walks the rendered .node-wrap elements in order, applying an .active class to each node box (scaling it up and coloring it indigo) for 420ms, then downgrading it to a .found green state and lighting up the arrow leading to the next node in amber, before moving on. This sequence — highlight current node, light the outgoing arrow, move to next node — is a literal animation of the traversal loop while (node !== null) { visit(node); node = node.next; }, making the O(n) nature of searching a linked list (you must walk from the head; there's no random-access jump to index 5) visible rather than assumed.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Hand this snippet's JavaScript to an AI assistant like Claude and ask it to explain exactly why deleteValue() awaits the removal transition before mutating the array, and why that ordering matters for the animation to look correct. Good extensions to ask for: a reverse() button that visibly flips every arrow's direction, a doubly linked list variant with both next and prev arrows, or a "search" mode that traverses and stops highlighting as soon as a target value is found instead of always walking the full list.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build an animated singly linked list visualizer in plain HTML, CSS, and JavaScript, no libraries or frameworks.

Requirements:
- Represent the list as an ordered collection of nodes, each with a stable unique id that persists across re-renders so a specific logical node's DOM element can be targeted individually.
- Render each node as a box connected to the next node by a directional arrow (CSS-drawn or SVG), with a "null" terminator shown after the final node.
- Support insert at head, insert at tail, and insert at a user-specified index, each triggering an entrance animation (fade/scale in) for the new node and its connecting arrow.
- Support delete by value: before removing the node from the underlying data, animate that specific node's exit (fade/scale out) and await that animation's completion, then update the data and re-render so the surrounding nodes' arrow reconnects cleanly rather than instantly snapping.
- Add a Traverse action that walks the list from the head, sequentially highlighting each node and its outgoing arrow with a short delay between steps, visually replaying a while-node-is-not-null pointer-following loop.
- In the status text or comments, explain why inserting at the head of a true pointer-based linked list is O(1) while the same operation on a plain JavaScript array (unshift) is O(n), even if the demo itself uses an array internally for rendering convenience.
- Keep all animation timing based on CSS transitions plus small async/await delays — no animation library, no canvas.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

Step by step

How to Use

  1. 1
    Type a value and click Insert Head or Insert TailA new node box fades and scales into view at the front or back of the chain, and the arrow connecting it to its neighbor fades in immediately after.
  2. 2
    Type a value and an index, then click Insert AtThe node is spliced into the exact position requested (clamped to the list's current bounds), and the whole chain re-renders showing the new node wired between its new neighbors.
  3. 3
    Type an existing value and click Delete ValueThe matching node box fades out and shrinks in place first — only after that removal animation finishes does the rest of the list re-render with the gap closed and a fresh arrow connecting the surrounding nodes.
  4. 4
    Click Traverse to walk the list step by stepStarting from the head, each node highlights indigo, then settles to green as the arrow leading to the next node lights up amber, visually replaying the .next pointer-following loop.
  5. 5
    Watch the null tag at the end of the chainThe final tag reading "null" represents the last node's .next pointer, reinforcing that a singly linked list always terminates in a null reference rather than looping back.
  6. 6
    Try deleting the head or tail specificallyDelete the first or last value shown and observe that only the arrow touching the deleted node changes — the rest of the chain's arrows and positions are undisturbed, matching how a real linked-list deletion only touches one neighboring pointer.

Real-world uses

Common Use Cases

Teaching linked lists and Big-O of common operations
Show why inserting at the head is O(1) for a true pointer-based linked list versus O(n) for an array's unshift(), using the animated arrow rewiring as the visual proof. Pair with the binary search visualizer to contrast array random-access with linked-list sequential-access data structures.
Interview preparation for linked-list manipulation problems
Reversing a linked list, detecting a cycle, and removing the nth node from the end are classic interview questions built entirely on pointer rewiring. Watching this visualizer's insert/delete operations reinforces exactly which references move and which stay fixed.
Documentation for a custom data structures library
Embed alongside API docs for a linked-list or deque implementation to give users an intuitive, interactive picture of insert/delete/traverse behavior before they read the method signatures.
Interactive demo for a data structures course or blog post
Drop into an article explaining linked lists as a live, clickable demo instead of a static diagram sequence. Fully self-contained with no build step, fits any UI snippets gallery or iframe embed.
Whiteboard-style walkthrough tool for technical mentoring
Use during 1:1 mentoring or onboarding sessions to build and modify a list live while explaining pointer semantics, rather than drawing boxes and arrows by hand on a whiteboard.

Got questions?

Frequently Asked Questions

Yes. In React, hold the list array in useState and let React's reconciliation handle entrance/exit — for exit animations specifically, either use a small animation library's AnimatePresence-style pattern or replicate this snippet's approach by delaying the state update with a setTimeout inside an async handler and clearing that timeout in a useEffect cleanup if the component unmounts mid-animation. In Vue, use Vue's built-in <TransitionGroup> for automatic enter/leave animations on list changes instead of manually orchestrating awaits. In Angular, use the Animations API's :enter/:leave triggers on an *ngFor, or replicate the manual await-before-splice pattern inside a component method, clearing any pending timers in ngOnDestroy.

A true linked list stores each node with an explicit reference to the next node; prepending only requires creating one new node and updating two references (the new node's next, and the list's head pointer) — no other node is touched, so it's O(1) regardless of list length. This snippet represents the list as a JavaScript array for rendering simplicity, and Array.prototype.unshift is actually O(n) under the hood because every existing element's index shifts. The status message still teaches the real linked-list complexity characteristic; if you need actual O(1) head insertion in production code, use real {value, next} node objects rather than an array.

Every node carries a stable id assigned once at creation time (genId()), stored both in the data array and as a data-id attribute on its rendered .node-wrap element. deleteValue() looks up the matching array index, then queries the DOM for the .node-wrap at that same position, adds a .removing class to trigger its exit transition, and only mutates the underlying array (via splice) after awaiting that transition's duration — which is what makes the fade-then-reflow sequence possible instead of an instant, jarring layout jump.

findIndex() returns the first matching index only, so the first occurrence (closest to the head) is deleted. To remove all occurrences, you would loop findIndex() and repeat the delete-and-await sequence until no match remains, or change the lookup to build a list of all matching indices up front and animate them out together.

Add a second arrow per node pointing in the reverse direction (back toward the previous node), style it distinctly, and update insert/delete operations to also acknowledge a conceptual "previous" pointer changing alongside the "next" pointer in the status messages. The rendering and animation approach — stable ids, await-before-splice deletes, entrance transitions on insert — carries over unchanged; only the number of arrows per node and the narration text need to change.