Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save carefree-ladka/7eed0fb09cab3021aacb4dd8766b1f5e to your computer and use it in GitHub Desktop.

Select an option

Save carefree-ladka/7eed0fb09cab3021aacb4dd8766b1f5e to your computer and use it in GitHub Desktop.
How JavaScript Works — Under the Hood

How JavaScript Works — Under the Hood 🧠

Most developers use JavaScript. Few understand what the engine actually does when your code runs. This series covers the internals — what no tutorial teaches but every senior dev is expected to know.

**Who is this for?** - 🔵 **Experienced devs** filling the "I know it works, but not *why*" gap - 🟢 **Ambitious freshers** who want to stand out in interviews - Anyone who has ever been confused by `this`, hoisting, closures, or async timing

Recommended order: Follow the sections top to bottom — each one builds on the previous.


Table of Contents

  1. The JavaScript Engine
  2. Memory — How JS Stores Data
  3. Execution Context
  4. The Global Execution Context
  5. Function Execution Context
  6. The Call Stack
  7. Scope & Scope Chain
  8. Hoisting — In Depth
  9. The this Keyword — Context Rules
  10. Closures — How & Why
  11. The Event Loop & Concurrency Model
  12. Garbage Collection
  13. Memory Leaks — Causes & Fixes
  14. V8 Engine Internals — JIT & Optimization

1. The JavaScript Engine

The starting point. Before any code runs, you need to understand what is actually executing it.

JavaScript is not interpreted line by line the naive way. Engines like V8 (Chrome, Node), SpiderMonkey (Firefox), and JavaScriptCore (Safari) do a lot of work before your first variable is set.

What the engine does

Source Code
    ↓
Tokenizer / Lexer      → breaks code into tokens
    ↓
Parser                 → builds an Abstract Syntax Tree (AST)
    ↓
Interpreter (Ignition) → produces bytecode, starts executing
    ↓
Profiler               → watches for "hot" code (run many times)
    ↓
JIT Compiler (TurboFan)→ compiles hot paths to machine code
    ↓
Optimized Machine Code → runs at near-native speed

Topics to cover

# Topic Format Notes
1.1 What is a JavaScript engine? 📹 Long V8, SpiderMonkey, JSCore overview
1.2 Lexing & Tokenization 📹 Long How source code becomes tokens
1.3 Abstract Syntax Tree (AST) 📹 Long Use astexplorer.net live demo
1.4 Interpreter vs Compiler 📹 Long Why JS is "both"
1.5 JIT Compilation basics ⚡ Short Hot functions, deoptimization

Example Short: Your JS code isn't interpreted — here's what actually happens ⚡
Example Long Video: Inside V8 — from source code to machine code, step by step


2. Memory — How JS Stores Data

Where do variables actually live? Why does let x = {} behave differently from let x = 5?

JavaScript has two memory regions. Understanding them explains reference bugs, shallow copy issues, and performance traps.

The two memory regions

┌─────────────────────────────────┐
│            STACK                │
│  ─────────────────────────────  │
│  Primitives stored by VALUE     │
│  Fixed size, fast access        │
│  let x = 5   → [5] on stack     │
│  let y = x   → [5] copy on stack│
└─────────────────────────────────┘

┌─────────────────────────────────┐
│             HEAP                │
│  ─────────────────────────────  │
│  Objects/Arrays stored by REF   │
│  Dynamic size, slower           │
│  let obj = {}  → ref on stack   │
│                  data on heap   │
│  let b = obj   → same ref!      │
└─────────────────────────────────┘

Primitives vs Reference types

// Primitives — copied by value
let a = 10;
let b = a;
b = 20;
console.log(a); // 10 — untouched

// Objects — copied by reference
let obj1 = { name: "Rahul" };
let obj2 = obj1;
obj2.name = "Priya";
console.log(obj1.name); // "Priya" — same reference!

Topics to cover

# Topic Format Notes
2.1 Stack vs Heap memory 📹 Long Visual walkthrough
2.2 Primitive types — stored by value 📹 Long number, string, boolean, null, undefined, symbol, bigint
2.3 Reference types — stored by reference 📹 Long objects, arrays, functions
2.4 Shallow copy vs Deep copy 📹 Long {...obj}, Object.assign, structuredClone
2.5 typeof and memory implications ⚡ Short typeof null === "object" explained
2.6 Memory allocation at runtime 📹 Long How the engine allocates per declaration

Example Short: Why obj2 = obj1 doesn't copy anything — reference trap explained 🎯
Example Long Video: Stack vs Heap — the memory model every JS dev must visualize
Project: Debug a broken deep-clone function — find the reference bugs live


3. Execution Context

The single most important concept to understand if you want to master JavaScript.

An Execution Context (EC) is the environment in which JavaScript code is evaluated and executed. Every time code runs, an EC is created.

The three types

┌──────────────────────────────────────────┐
│         EXECUTION CONTEXT TYPES          │
├──────────────────────────────────────────┤
│ 1. Global Execution Context (GEC)        │
│    → Created once when the script loads  │
│    → Creates: global object + `this`     │
│                                          │
│ 2. Function Execution Context (FEC)      │
│    → Created every time a function runs  │
│    → Has its own Variable Environment    │
│                                          │
│ 3. Eval Execution Context                │
│    → Created inside eval() — avoid it   │
└──────────────────────────────────────────┘

Two phases of every Execution Context

Phase 1: CREATION (Memory Phase)
─────────────────────────────────
  → Scan for variables → assign undefined
  → Scan for functions → store full definition
  → Set up scope chain
  → Determine value of `this`

Phase 2: EXECUTION (Code Phase)
─────────────────────────────────
  → Run code line by line
  → Assign actual values to variables
  → Execute function calls (creates new EC)

Topics to cover

# Topic Format Notes
3.1 What is an Execution Context? 📹 Long The core mental model
3.2 Creation Phase vs Execution Phase 📹 Long Memory allocation, then running
3.3 Variable Environment 📹 Long Where identifiers live inside an EC
3.4 Lexical Environment 📹 Long EC + reference to outer EC
3.5 The three types of EC ⚡ Short GEC, FEC, Eval

Example Long Video: Execution Context — the engine's blueprint for running your code


4. The Global Execution Context

The EC that exists before your first line of code runs.

What gets created

Global Execution Context
├── Global Object         → window (browser) / global (Node)
├── this                  → points to the global object
├── Outer Environment     → null (nothing above global)
└── Variable Environment  → all var declarations + function defs

The two phases — live example

var name = "Rahul";
var age = 25;

function greet() {
  console.log("Hello", name);
}

greet();
CREATION PHASE (what engine does first):
─────────────────────────────────────────
  name  → undefined
  age   → undefined
  greet → function greet() { ... }   ← full definition stored

EXECUTION PHASE (running line by line):
─────────────────────────────────────────
  name  = "Rahul"
  age   = 25
  greet() → creates a new Function Execution Context

Topics to cover

# Topic Format Notes
4.1 Global EC creation 📹 Long window/global object, this
4.2 Global object in browser vs Node ⚡ Short window vs global vs globalThis
4.3 How var declarations live on the global object 📹 Long var x = 1window.x
4.4 Why let/const don't attach to global object 📹 Long Block scope in GEC
4.5 Live walkthrough — creation + execution phase 📹 Long Step-by-step with diagrams

Example Short: Why window.x works but window.y doesn't — var vs let in global scope 🌍
Example Long Video: Global Execution Context — what runs before your code does


5. Function Execution Context

Every function call creates its own universe. Here's what's inside it.

What a Function EC contains

Function Execution Context (created on every call)
├── Variable Environment   → local vars, arguments object
├── arguments object       → array-like list of passed args
├── this                   → depends on HOW the function is called
├── Outer Environment Ref  → points to where function was DEFINED
└── Scope Chain            → chain of outer environments

Example — step by step

var x = 1;

function outer() {
  var y = 2;

  function inner() {
    var z = 3;
    console.log(x, y, z); // 1, 2, 3
  }

  inner();
}

outer();
Call Stack state:
─────────────────
  [inner EC]   ← currently running
  [outer EC]   ← paused, waiting
  [Global EC]  ← paused, waiting

inner EC:
  z = 3
  outer ref → outer EC
  outer EC ref → Global EC   ← scope chain!

Topics to cover

# Topic Format Notes
5.1 Function EC creation 📹 Long What is created on every call
5.2 The arguments object 📹 Long vs rest parameters
5.3 this inside functions 📹 Long Strict vs non-strict mode
5.4 Lexical environment & outer reference 📹 Long Foundation of closures
5.5 Arrow functions — no own EC for this 📹 Long Why arrow functions inherit this
5.6 Function EC lifecycle ⚡ Short Created → runs → destroyed

Example Short: Arrow functions don't have their own \this` — here's exactly why 🎯 **Example Long Video:**Function Execution Context — full walkthrough with nested calls **Project:**Trace any function's EC manually — build a visualizer tool`


6. The Call Stack

The data structure that tracks where the engine is at any given moment.

The call stack is a LIFO (Last In, First Out) stack of execution contexts. It is the engine's way of knowing "what is currently running, and where do I go back when it finishes."

Visual walkthrough

function a() { b(); }
function b() { c(); }
function c() { console.log("deep"); }

a();
Step 1:  [Global EC]                    ← a() called
Step 2:  [a EC] [Global EC]             ← b() called inside a
Step 3:  [b EC] [a EC] [Global EC]      ← c() called inside b
Step 4:  [c EC] [b EC] [a EC] [GEC]     ← console.log runs
Step 5:  [b EC] [a EC] [Global EC]      ← c EC popped
Step 6:  [a EC] [Global EC]             ← b EC popped
Step 7:  [Global EC]                    ← a EC popped
Step 8:  []                             ← script done

Stack overflow

function recurse() {
  recurse(); // no base case
}
recurse(); // RangeError: Maximum call stack size exceeded

Topics to cover

# Topic Format Notes
6.1 What is the Call Stack? 📹 Long LIFO, push/pop, execution order
6.2 Stack frames — what's in each frame 📹 Long EC per frame
6.3 Stack overflow — cause & diagnosis 📹 Long Infinite recursion, Chrome DevTools
6.4 Reading the call stack in DevTools 📹 Long Practical debugging skill
6.5 Single-threaded nature of JS ⚡ Short One call stack = one thing at a time

Example Short: Stack Overflow in JS — what it actually means and how to debug it 🔥
Example Long Video: The Call Stack — visualized from zero to stack overflow


7. Scope & Scope Chain

Where can a variable be accessed from? The scope chain answers this.

The three scope types

┌─────────────────────────────────────────┐
│  GLOBAL SCOPE                           │
│  var / let / const at top level         │
│  ┌───────────────────────────────────┐  │
│  │  FUNCTION SCOPE                   │  │
│  │  var declared inside a function   │  │
│  │  ┌─────────────────────────────┐  │  │
│  │  │  BLOCK SCOPE                │  │  │
│  │  │  let / const inside {}      │  │  │
│  │  └─────────────────────────────┘  │  │
│  └───────────────────────────────────┘  │
└─────────────────────────────────────────┘

Scope chain lookup

let global = "G";

function outer() {
  let o = "O";
  function inner() {
    let i = "I";
    console.log(i);       // found in inner EC
    console.log(o);       // not in inner → look in outer → found
    console.log(global);  // not in inner/outer → look in GEC → found
  }
  inner();
}
outer();

Topics to cover

# Topic Format Notes
7.1 Global, Function, Block scope 📹 Long With var / let / const differences
7.2 Lexical scope — defined at write time 📹 Long Not at call time
7.3 Scope chain — how lookup works 📹 Long Walking outer references
7.4 Variable shadowing ⚡ Short Inner var hiding outer var
7.5 var leaking out of blocks ⚡ Short The classic var bug in loops
7.6 Temporal Dead Zone revisited 📹 Long let/const in creation phase

Example Short: var in a for loop — the bug that's bitten every JS developer 🐛
Example Long Video: Scope Chain — how JS finds your variables, step by step


8. Hoisting — In Depth

Variables and functions aren't where you put them. Here's where the engine puts them.

Hoisting is a direct result of the Creation Phase of the Execution Context. The engine scans the code before executing it.

What gets hoisted and how

// What you write:
console.log(x);     // undefined — not an error
greet();            // "Hello" — works fine
sayBye();           // TypeError — not a function

var x = 5;
function greet() { console.log("Hello"); }
var sayBye = function() { console.log("Bye"); };

// What the engine sees after creation phase:
var x = undefined;
var sayBye = undefined;
function greet() { console.log("Hello"); }
// ... then execution begins

let and const — hoisted but not initialized

console.log(a); // ReferenceError: Cannot access 'a' before initialization
let a = 10;

// 'a' IS in memory (hoisted) but in the Temporal Dead Zone
// until the let declaration line is reached in execution

Topics to cover

# Topic Format Notes
8.1 What hoisting really is 📹 Long Creation phase memory allocation
8.2 var hoisting — set to undefined 📹 Long
8.3 Function declaration hoisting — fully hoisted 📹 Long vs function expressions
8.4 let / const — TDZ explained 📹 Long Hoisted but inaccessible
8.5 Class hoisting ⚡ Short Classes are NOT fully hoisted
8.6 Hoisting in nested functions 📹 Long Per-EC hoisting behavior

Example Short: Classes aren't hoisted like functions — this one surprises everyone 😲
Example Long Video: Hoisting fully explained — var, let, const, functions, classes


9. The this Keyword — Context Rules

this doesn't mean what you think it means. It depends entirely on how a function is called.

The four rules of this

Rule 1 — Default binding (standalone call)
  function greet() { console.log(this); }
  greet();       → window (browser) | undefined (strict mode)

Rule 2 — Implicit binding (method call)
  const obj = { name: "Rahul", greet() { console.log(this.name); } }
  obj.greet();   → this = obj → "Rahul"

Rule 3 — Explicit binding (call / apply / bind)
  greet.call(obj)   → this = obj
  greet.apply(obj)  → this = obj
  greet.bind(obj)() → this = obj (returns new function)

Rule 4 — new binding (constructor call)
  function Person(name) { this.name = name; }
  const p = new Person("Rahul");  → this = new object

Arrow functions — no this of their own

const obj = {
  name: "Rahul",
  greet: function() {
    const inner = () => {
      console.log(this.name); // "Rahul" — inherits from greet's EC
    };
    inner();
  }
};
obj.greet();

Topics to cover

# Topic Format Notes
9.1 What this actually refers to 📹 Long Call-site determination
9.2 Default binding 📹 Long Strict vs non-strict
9.3 Implicit binding & binding loss 📹 Long The callback this trap
9.4 Explicit binding — call, apply, bind 📹 Long Differences + use cases
9.5 new binding — constructor context 📹 Long What new does step by step
9.6 Arrow functions & lexical this 📹 Long Inheriting from enclosing EC
9.7 this in classes ⚡ Short Method binding in class instances
9.8 this priority — which rule wins? ⚡ Short new > explicit > implicit > default

Example Short: Lost your \this` in a callback? Here's why and the fix ✅ **Example Long Video:**this — all four binding rules with real bugs and fixes **Project:**Build a custom bind() polyfill from scratch`


10. Closures — How & Why

Closures are not a feature. They are a natural consequence of lexical scope + function EC lifetime.

How a closure forms

function makeCounter() {
  let count = 0;              // lives in makeCounter's EC

  return function increment() {
    count++;                  // accesses outer EC — closure!
    console.log(count);
  };
}

const counter = makeCounter(); // makeCounter EC "done" — but count survives!
counter(); // 1
counter(); // 2
counter(); // 3

Why the outer variable survives

Normal:   EC runs → EC destroyed → variables gone
Closure:  EC runs → inner function holds reference to outer variables
          → Garbage Collector cannot collect them
          → They stay in memory as long as inner function exists

Topics to cover

# Topic Format Notes
10.1 What creates a closure 📹 Long Lexical scope + returned functions
10.2 Closure over the variable, not the value 📹 Long The classic var in loop bug
10.3 Practical closure patterns 📹 Long Counters, private state, once()
10.4 Closures in async code 📹 Long setTimeout + loop trap
10.5 Memory implications of closures 📹 Long When they cause leaks
10.6 Module pattern using closures 📹 Long Private + public API

Example Short: The var-in-loop bug — closures explained in 90 seconds 🔄
Example Long Video: Closures — not magic, just lexical scope + persistent EC
Project: Build a private counter module using only closures — no classes


11. The Event Loop & Concurrency Model

JavaScript is single-threaded. But it handles async tasks. Here's how that's possible.

The full picture

┌──────────────────────────────────────────────────────────┐
│                    JS ENGINE                             │
│                                                          │
│   Call Stack          Memory Heap                        │
│   ──────────          ───────────                        │
│   [main()]            {objects,                          │
│   [fn()]              arrays, fns                        │
│                       stored here}                       │
└──────────────────────────────────────────────────────────┘

┌──────────────────┐     ┌────────────────────────────────┐
│   Web APIs       │     │        TASK QUEUES             │
│  (Browser/Node)  │     │                                │
│  setTimeout      │────▶│  Macrotask Queue               │
│  fetch           │     │  [setTimeout cb] [setInterval] │
│  DOM events      │     │                                │
│  setInterval     │     │  Microtask Queue (priority)    │
└──────────────────┘     │  [Promise .then] [queueMicro]  │
                         └────────────────────────────────┘
                                    ↕
                            EVENT LOOP
                   (moves tasks to Call Stack
                    when stack is empty)

Microtask vs Macrotask priority

console.log("1 - sync");

setTimeout(() => console.log("2 - macrotask"), 0);

Promise.resolve().then(() => console.log("3 - microtask"));

console.log("4 - sync");

// Output:
// 1 - sync
// 4 - sync
// 3 - microtask   ← microtasks drain before macrotasks
// 2 - macrotask

Topics to cover

# Topic Format Notes
11.1 Why JS needs an event loop 📹 Long Single thread + async tasks
11.2 Web APIs — where async tasks live 📹 Long setTimeout, fetch, DOM events
11.3 Macrotask queue (Task Queue) 📹 Long setTimeout, setInterval, I/O
11.4 Microtask queue 📹 Long Promises, queueMicrotask, MutationObserver
11.5 Microtask vs Macrotask — priority rules 📹 Long Microtasks drain first, always
11.6 requestAnimationFrame timing ⚡ Short Where it fits in the loop
11.7 Blocking the event loop 📹 Long What heavy sync code does to UI
11.8 Node.js event loop differences 📹 Long libuv, phases, I/O callbacks
11.9 Output-based event loop questions ⚡ Short series Interview prep

Example Short: Microtask vs Macrotask — which runs first? Most people get this wrong 🤯
Example Long Video: The Event Loop — complete visual walkthrough with all queues
Project: Build a task scheduler that demonstrates microtask/macrotask ordering


12. Garbage Collection

Memory you're not using should be freed. How does the engine decide what to free?

JavaScript manages memory automatically. But "automatic" doesn't mean "perfect." Understanding GC helps you write code that doesn't hold memory longer than needed.

Mark-and-Sweep algorithm (modern engines)

Step 1 — MARK
  → Start from "roots" (global object, call stack variables)
  → Traverse all references reachable from roots
  → Mark every reachable object

Step 2 — SWEEP
  → Walk through the entire heap
  → Any object NOT marked → unreachable → free its memory

Step 3 — COMPACT (optional)
  → Defragment heap — move remaining objects together
  → Faster future allocations

Reference counting (old approach — replaced)

// Reference counting fails on circular references:
function leak() {
  let a = {};
  let b = {};
  a.ref = b;   // a holds b
  b.ref = a;   // b holds a
  // both have count > 0 — never freed
  // mark-and-sweep handles this correctly — neither reachable from root
}

Topics to cover

# Topic Format Notes
12.1 How JS manages memory automatically 📹 Long Allocation → use → deallocation
12.2 Reachability — the core GC concept 📹 Long Roots, references, reachable graph
12.3 Mark-and-Sweep algorithm 📹 Long The modern approach
12.4 Reference counting — why it was replaced 📹 Long Circular reference problem
12.5 Generational GC in V8 📹 Long Young gen (Scavenger) vs Old gen
12.6 GC pauses and performance ⚡ Short Stop-the-world vs incremental GC
12.7 WeakRef and FinalizationRegistry 📹 Long Modern GC-aware APIs
12.8 WeakMap / WeakSet — GC-friendly storage 📹 Long Keys held weakly

Example Short: WeakMap vs Map — why one lets the GC clean up and the other doesn't 🗑️
Example Long Video: Garbage Collection in V8 — Mark-Sweep, generations, and pauses


13. Memory Leaks — Causes & Fixes

Code that works is not the same as code that doesn't leak. This section covers the real-world causes.

The four most common memory leaks

// 1. Forgotten global variables
function oops() {
  leaked = "I'm global now"; // no var/let/const → window.leaked
}

// 2. Detached DOM nodes held in JS
const btn = document.getElementById("btn");
document.body.removeChild(btn); // removed from DOM
// but 'btn' variable still holds reference → node can't be GC'd

// 3. Timers not cleared
const id = setInterval(() => {
  doWork(bigData); // bigData stays in memory as long as timer runs
}, 1000);
// must call clearInterval(id) when done

// 4. Closures holding large data unintentionally
function setup() {
  const hugeData = new Array(1000000).fill("data");
  return function() {
    console.log("running"); // closes over hugeData — keeps it alive
  };
}

Topics to cover

# Topic Format Notes
13.1 What is a memory leak? 📹 Long Allocated but never freed
13.2 Accidental globals 📹 Long Missing declarations
13.3 Detached DOM nodes 📹 Long Common in SPAs
13.4 Unclosed timers & intervals 📹 Long setInterval cleanup pattern
13.5 Event listeners not removed 📹 Long addEventListener + removeEventListener
13.6 Closures holding large data 📹 Long Unintended long-lived references
13.7 Finding leaks with Chrome DevTools 📹 Long Heap snapshots, allocation timeline
13.8 WeakMap / WeakRef as fixes 📹 Long Let GC do its job

Example Long Video: Find and fix a memory leak live — Chrome DevTools heap snapshot walkthrough
Project: Diagnose a deliberately leaky SPA — find all 4 leak types with DevTools


14. V8 Engine Internals — JIT & Optimization

The advanced section. Writing code the engine can optimize — and understanding why it sometimes can't.

How V8 optimizes your code

All functions start "unoptimized"
         ↓
Ignition (interpreter) runs them as bytecode
         ↓
Profiler watches for "hot" functions (called many times)
         ↓
TurboFan (JIT compiler) compiles hot functions to machine code
         ↓
Fast execution — until type changes cause deoptimization

Hidden classes — shape optimization

// V8 builds a "hidden class" (shape) for each object structure
// Consistent shapes = fast property access

// FAST — same shape
function Point(x, y) { this.x = x; this.y = y; }
const p1 = new Point(1, 2); // shape: {x, y}
const p2 = new Point(3, 4); // same shape → V8 can optimize

// SLOW — different shapes
const a = {};
a.x = 1;         // shape A: {x}
a.y = 2;         // shape B: {x, y} — new hidden class
const b = {};
b.y = 2;         // shape C: {y} — different from A
b.x = 1;         // shape D: {y, x} — different from B
// a and b have DIFFERENT shapes despite same final state

Topics to cover

# Topic Format Notes
14.1 V8 pipeline — Ignition + TurboFan 📹 Long Bytecode → machine code
14.2 Hidden classes (object shapes) 📹 Long Property order matters
14.3 Inline caching 📹 Long How repeated calls get faster
14.4 Deoptimization — and what triggers it 📹 Long Type changes, arguments object
14.5 Monomorphic vs polymorphic call sites 📹 Long One shape vs many shapes
14.6 Practical: how to write V8-friendly code 📹 Long Actionable patterns
14.7 console.time + profiler for benchmarking 📹 Long Measure before optimizing

Example Short: Property order in objects affects performance — here's why ⚡
Example Long Video: How V8 JIT compiles your code — and what makes it give up
Project: Benchmark two implementations of the same function — find which V8 optimizes better


Series Summary — Learning Path

Week 1   → Sections 1 + 2   Engine overview + Memory model
Week 2   → Sections 3 + 4   Execution Context + Global EC
Week 3   → Sections 5 + 6   Function EC + Call Stack
Week 4   → Sections 7 + 8   Scope Chain + Hoisting
Week 5   → Section 9        this keyword — all four rules
Week 6   → Section 10       Closures — deep dive
Week 7   → Section 11       Event Loop — complete model
Week 8   → Sections 12 + 13 Garbage Collection + Memory Leaks
Week 9   → Section 14       V8 Internals — optimization
**Suggested video format for this series:** Every video should open with a **wrong mental model** (what most people think), then replace it with the **correct one** (what the engine actually does). This structure converts well — viewers share the "I was wrong about this" moment.

Part of the JavaScript Complete Roadmap series. This playlist pairs with the main roadmap — specifically deepening Sections 3, 4, 8, and 9 of the core playlist.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment