Playground Component
Detect Cycle
In Linked List Visualizer
An interactive Floyd cycle detection visualization that shows slow and fast pointers moving through a linked list to detect a loop.

Detect Cycle in a Linked List
Slow
25
Fast
25
Steps
0
Cycle
Yes
Action
-
Status
Ready
Linked List
Head
SlowFast
25
48
74
72
36
Algorithm
1slow = head; fast = head;2while (fast && fast.next) {3 slow = slow.next;4 fast = fast.next.next;5 if (slow === fast) return true;6}7return false;
Copy Code
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
<title>Detect Cycle in a Linked List</title>
<style>
:root{--bg:#090909;--panel:#111;--gold:#f4c430;--gold-dark:#b88700;--text:#f5f5f5;--border:#2f2f2f}*{box-sizing:border-box}body{margin:0}.cycle-viz{min-height:100vh;padding:40px;background:radial-gradient(circle at top,#1a1505 0%,#090909 40%);color:var(--text);font-family:Poppins,Arial,sans-serif}.cycle-viz h2{text-align:center;font-size:3rem;color:var(--gold);margin:0 0 35px}.cycle-controls{display:flex;justify-content:center;gap:15px;flex-wrap:wrap;margin-bottom:35px}.cycle-button{padding:14px 22px;border:0;border-radius:12px;cursor:pointer;font-weight:600;font-size:15px;background:linear-gradient(180deg,#ffd54d,#d9a900);color:#111;display:inline-flex;align-items:center;gap:10px;transition:.25s}.cycle-button:hover{transform:translateY(-2px)}.cycle-button:disabled{cursor:not-allowed;opacity:.55;transform:none}.toggle-on{background:linear-gradient(180deg,#43e97b,#1fae57);color:#08210f}.toggle-off{background:#1c1c1c;color:#ccc;border:1px solid #3a3a3a}.cycle-stats{width:100%;max-width:960px;margin:auto;margin-bottom:40px;display:grid;grid-template-columns:1fr 1fr 1fr 1fr 3fr 1fr;gap:18px}.cycle-stat-box{background:#151515;border:1px solid #2b2b2b;border-radius:14px;padding:18px;text-align:center}.cycle-stat-box h4{margin:0;color:#999;font-size:.9rem}.cycle-stat-box p{margin:8px 0 0;color:var(--gold);font-size:1.4rem;font-weight:bold}.cycle-code,.cycle-stage{width:100%;max-width:1180px;margin:0 auto 30px;padding:24px 26px 20px;border:1px solid var(--border);border-radius:18px;background:#111}.cycle-code h3,.cycle-stage h3{margin:0 0 16px;text-align:center;color:var(--gold);font-size:1.6rem}.cycle-code-block{margin:0;font-family:"Fira Code","Courier New",monospace;font-size:1rem;line-height:1.9;background:#0c0c0c;border:1px solid #222;border-radius:12px;padding:16px 10px}.cycle-code-line{display:flex;gap:16px;padding:2px 14px;border-left:3px solid transparent;border-radius:6px;color:#8a8a8a;white-space:pre;transition:.3s}.cycle-code-linenum{color:#4a4a4a;font-size:.85rem;min-width:16px;text-align:right}.cycle-code-line.active{color:var(--gold);background:rgba(244,196,48,.1);border-left-color:var(--gold);transform:translateX(4px);animation:glow 1.1s ease-in-out infinite}.cycle-code-line.active .cycle-code-linenum{color:var(--gold)}@keyframes glow{50%{box-shadow:0 0 18px rgba(244,196,48,.32) inset,0 0 20px rgba(244,196,48,.28)}}.cycle-stage{padding:28px 30px 34px}.cycle-stage h3{margin-bottom:28px}.cycle-chain-scroll{overflow-x:auto;padding-bottom:14px}.cycle-chain{position:relative;display:flex;align-items:flex-start;gap:10px;width:max-content;min-height:250px;padding:10px 30px 0}.cycle-svg-overlay{position:absolute;top:0;left:0;width:100%;height:100%;overflow:visible;pointer-events:none}.cycle-arc{fill:none;stroke:var(--gold);stroke-width:3;stroke-dasharray:6 6;opacity:.85;filter:drop-shadow(0 0 6px rgba(244,196,48,.4))}.cycle-label,.cycle-null{color:var(--gold);font-weight:700;font-size:1rem;white-space:nowrap;margin-top:52px}.cycle-node-wrap{display:flex;flex-direction:column;align-items:center;gap:8px;min-width:82px;overflow:visible}.cycle-pointer-stack{width:100%;height:44px;display:flex;align-items:flex-end;justify-content:center;flex-wrap:wrap;gap:4px}.cycle-pointer-badge{position:relative;z-index:2;min-width:34px;min-height:18px;padding:2px 5px;border:1px solid #343434;border-radius:5px;background:#111;color:#999;display:inline-flex;align-items:center;justify-content:center;font-size:.68rem;font-weight:700;text-transform:uppercase;transition:.45s}.slow-pointer{border-color:#4ea8ff;color:#9cc9ff}.fast-pointer{border-color:#ff6b6b;color:#ffaaa8}.slow-pointer.moving,.fast-pointer.moving{transform:translateY(-6px) scale(1.08)}.cycle-node{width:72px;height:72px;border-radius:14px;background:#171717;border:2px solid #2c2c2c;color:var(--text);display:flex;align-items:center;justify-content:center;font-size:1.3rem;font-weight:700;transition:.25s}.cycle-node.slow-visit{background:rgba(78,168,255,.15)}.cycle-node.fast-visit{background:rgba(255,107,107,.15)}.cycle-node.slow{border-color:#4ea8ff}.cycle-node.fast{border-color:#ff6b6b;transform:translateY(-4px)}.cycle-node.slow.fast{border-color:var(--gold);box-shadow:0 0 18px rgba(244,196,48,.35)}.cycle-node.meeting{background:#25c05a;color:#fff;border-color:#43e97b;transform:scale(1.14);box-shadow:0 0 24px rgba(37,192,90,.5)}.cycle-arrow{position:relative;width:78px;height:22px;flex:0 0 78px;margin-top:52px}.cycle-arrow:before{content:"";position:absolute;left:0;right:13px;top:50%;height:4px;border-radius:999px;background:var(--gold);transform:translateY(-50%)}.cycle-arrow:after{content:"";position:absolute;right:0;top:50%;border-top:11px solid transparent;border-bottom:11px solid transparent;border-left:17px solid var(--gold);transform:translateY(-50%)}.cycle-dot{width:20px;height:20px;border-radius:50%;background:var(--gold-dark);margin-top:58px;box-shadow:0 0 18px rgba(244,196,48,.3)}@media(max-width:768px){.cycle-viz{padding:20px}.cycle-viz h2{font-size:2rem}.cycle-stats{grid-template-columns:repeat(2,1fr)}.cycle-code,.cycle-stage{padding:18px 14px 14px}.cycle-code-block{font-size:.85rem}.cycle-node{width:58px;height:58px}.cycle-node-wrap{min-width:58px}.cycle-arrow{width:56px;flex-basis:56px}}
</style>
</head>
<body><main class="cycle-viz"><h2>Detect Cycle in a Linked List</h2><div class="cycle-controls"><button id="randomBtn" class="cycle-button">Randomize</button><button id="cycleBtn" class="cycle-button"></button><button id="detectBtn" class="cycle-button">Detect Cycle</button><button id="pauseBtn" class="cycle-button">Pause</button><button id="speedBtn" class="cycle-button"></button><button id="resetBtn" class="cycle-button">Reset</button></div><div id="stats" class="cycle-stats"></div><section class="cycle-code"><h3>Algorithm</h3><pre id="code" class="cycle-code-block"></pre></section><section class="cycle-stage"><h3>Linked List</h3><div class="cycle-chain-scroll"><div id="chain" class="cycle-chain"></div></div></section></main>
<script>
const N=5,speedLevels=[["Very Slow",900],["Slow",650],["Normal",450],["Fast",260],["Very Fast",120]],codeLines=["slow = head; fast = head;","while (fast && fast.next) {"," slow = slow.next;"," fast = fast.next.next;"," if (slow === fast) return true;","}","return false;"],lineMap={"Check loop condition":1,"Move slow":2,"Move fast (hop 1)":3,"Move fast (hop 2)":3,"Check meeting point":4,"Reached end":6};
let values,nodeStates,hasCycle=true,cycleEntry=2,slowIndex=0,fastIndex=0,transitIndex=null,movingPointer=null,steps=0,action="-",status="Ready",speedIndex=2,running=false,paused=false;
const sleep=ms=>new Promise(async r=>setTimeout(async()=>{while(paused)await new Promise(x=>setTimeout(x,120));r()},ms));function randomValues(){let used=new Set(),a=[];while(a.length<N){let v=Math.floor(Math.random()*90)+10;if(!used.has(v)){used.add(v);a.push(v)}}return a}function nextIndex(i,cycle=hasCycle,entry=cycleEntry){if(i===null)return null;if(i===N-1)return cycle?entry:null;return i+1}
function reset(force=false){if(running&&!force)return;paused=false;nodeStates=Array(N).fill("default");slowIndex=0;fastIndex=0;transitIndex=null;movingPointer=null;steps=0;action="-";status="Ready";running=false;render()}function mark(i,s){if(i!==null)nodeStates[i]=s}
function render(){randomBtn.disabled=running;cycleBtn.disabled=running;detectBtn.disabled=running||status==="Cycle Detected"||status==="No Cycle";pauseBtn.disabled=!running;pauseBtn.textContent=paused?"Play":"Pause";cycleBtn.textContent="Cycle: "+(hasCycle?"On":"Off");cycleBtn.className="cycle-button "+(hasCycle?"toggle-on":"toggle-off");speedBtn.textContent="Speed: "+speedLevels[speedIndex][0];stats.innerHTML=[["Slow",values[slowIndex]],["Fast",fastIndex===null?"NULL":values[fastIndex]],["Steps",steps],["Cycle",hasCycle?"Yes":"No"],["Action",action],["Status",status]].map(x=>`<div class="cycle-stat-box"><h4>${x[0]}</h4><p>${x[1]}</p></div>`).join("");let active=lineMap[action];code.innerHTML=codeLines.map((l,i)=>`<div class="cycle-code-line ${active===i?"active":""}"><span class="cycle-code-linenum">${i+1}</span><span>${l}</span></div>`).join("");let fd=transitIndex!==null?transitIndex:fastIndex,html=`<span class="cycle-label">Head</span><span class="cycle-arrow"></span>`;values.forEach((v,i)=>{let cls=["cycle-node",nodeStates[i]];if(i===slowIndex)cls.push("slow");if(i===fd)cls.push("fast");let badges="";if(i===slowIndex)badges+=`<span class="cycle-pointer-badge slow-pointer ${movingPointer==="slow"?"moving":""}">Slow</span>`;if(i===fd)badges+=`<span class="cycle-pointer-badge fast-pointer ${movingPointer==="fast"?"moving":""}">Fast</span>`;html+=`<div class="cycle-node-wrap"><div class="cycle-pointer-stack">${badges}</div><div class="${cls.join(" ")}">${v}</div></div>`;if(i<N-1)html+=`<span class="cycle-arrow"></span>`});if(!hasCycle)html+=`<span class="cycle-arrow"></span><span class="cycle-dot"></span><span class="cycle-null">NULL</span>`;else html+=`<svg class="cycle-svg-overlay"><defs><marker id="cycleArrowHead" markerWidth="10" markerHeight="10" refX="7" refY="5" orient="auto"><path d="M0,0 L10,5 L0,10 z" fill="var(--gold)"></path></marker></defs><path d="${cyclePath()}" class="cycle-arc" marker-end="url(#cycleArrowHead)"></path></svg>`;chain.innerHTML=html}
function cyclePath(){let start=170+N*170,end=170+cycleEntry*170;return`M ${start} 145 C ${start} 215, ${end} 215, ${end} 145`}
async function detect(){if(running)return;reset(true);running=true;status="Running";let slow=0,fast=0,count=0;render();while(true){action="Check loop condition";render();await sleep(speedLevels[speedIndex][1]/2);let fastNext=nextIndex(fast);if(fast===null||fastNext===null){status="No Cycle";action="Reached end";break}action="Move slow";movingPointer="slow";render();await sleep(speedLevels[speedIndex][1]);slow=nextIndex(slow);slowIndex=slow;movingPointer=null;mark(slow,"slow-visit");render();action="Move fast (hop 1)";movingPointer="fast";transitIndex=fastNext;render();await sleep(speedLevels[speedIndex][1]);action="Move fast (hop 2)";render();let fastAfter=nextIndex(fastNext);await sleep(speedLevels[speedIndex][1]);fast=fastAfter;fastIndex=fast;transitIndex=null;movingPointer=null;if(fast!==null)mark(fast,"fast-visit");steps=++count;action="Check meeting point";render();await sleep(speedLevels[speedIndex][1]);if(fast!==null&&slow===fast){status="Cycle Detected";mark(slow,"meeting");break}if(fast===null){status="No Cycle";action="Reached end";break}}running=false;paused=false;render()}
randomBtn.onclick=()=>{if(running)return;values=randomValues();if(hasCycle)cycleEntry=Math.floor(Math.random()*(N-1));reset(true)};cycleBtn.onclick=()=>{if(running)return;hasCycle=!hasCycle;if(hasCycle)cycleEntry=Math.floor(Math.random()*(N-1));reset(true)};detectBtn.onclick=detect;pauseBtn.onclick=()=>{paused=!paused;status=paused?"Paused":"Running";render()};speedBtn.onclick=()=>{speedIndex=(speedIndex+1)%speedLevels.length;render()};resetBtn.onclick=()=>reset();values=randomValues();reset(true);
</script></body></html>
About this Component
Cycle detection in a linked list checks whether following next pointers eventually loops back to an earlier node. This visualizer uses Floyd's tortoise and hare technique. The slow pointer moves one step at a time while the fast pointer moves two steps, making it easy to see how they meet when a cycle exists. Floyd's cycle detection runs in O(n) time and uses O(1) extra space, making it a classic two-pointer algorithm for linked lists.
Explore More Components
Discover more components from the Playground.



