Scrimba Guide — Vibe Coding Survival Series

The 10 JavaScript Concepts Every Vibe Coder Must Know

The concepts behind 90% of vibe coding bugs — with plain-English explanations and the fix for each.

1 async/await and Promises

Bug: "It returns undefined instead of the data."

JavaScript is non-blocking. If you call an async function without await, you get a Promise object, not the result.

// Wrong — data is a Promise, not the result const data = fetch(url); // Right const data = await fetch(url); const json = await data.json();
Learn it: Scrimba → JavaScript Promises course

2 undefined vs null

Bug: "Cannot read properties of undefined."

undefined means a variable was declared but never assigned. null means intentionally empty. Always check before accessing properties.

// Safe access with optional chaining const name = user?.profile?.name; // Default with nullish coalescing const label = name ?? 'Anonymous';
Learn it: Scrimba → Learn JavaScript (optional chaining)

3 Array mutation vs new arrays

Bug: "The UI doesn't update when I change the array."

React (and most state systems) detect changes by reference. Mutating an existing array doesn't trigger a re-render. Always return a new array.

// Mutates — React won't re-render items.push(newItem); setState(items); // Creates new array — React sees change setState([...items, newItem]);
Learn it: Scrimba → Learn React (state)

4 Closures and stale state

Bug: "The counter value is always 0 inside the event handler."

A closure captures the value of a variable at the time it was created. If state updates after the closure is created, it still sees the old value.

// Stale closure — count is always 0 onClick={() => setCount(count + 1)}; // Functional update — always fresh onClick={() => setCount(prev => prev + 1)};
Learn it: Scrimba → Learn JavaScript (closures)

5 The Event Loop

Bug: "This setTimeout runs before the data is ready."

JavaScript runs one thing at a time. Async operations (fetch, setTimeout) are queued and run only when the call stack is empty. You can't "wait" synchronously.

// Wrong — data isn't ready yet fetchData(); console.log(data); // undefined // Right — wait for the promise const data = await fetchData(); console.log(data); // ✓
Learn it: Scrimba → JavaScript async courses

6 Object reference equality

Bug: "useEffect runs on every render even though nothing changed."

Two objects with identical content are NOT equal in JavaScript — they're different references in memory. React's dependency array comparison uses reference equality.

// These are NOT equal {} === {} // false // Fix: memoize objects in deps const opts = useMemo(() => ({...}), []);
Learn it: Scrimba → Learn React (useMemo, useCallback)

7 Type coercion

Bug: "1 + '1' returns '11', not 2."

JavaScript silently converts types. The + operator prioritises string concatenation. Use === (strict) not == (loose) for comparisons.

1 + '1' // "11" (string) 1 - '1' // 0 (number) 0 == false // true (coercion!) 0 === false // false (strict) // Parse input values explicitly parseInt(input.value, 10);
Learn it: Scrimba → Learn JavaScript (types)

8 Scope and hoisting

Bug: "I can use the variable before I define it and it's undefined."

var declarations are hoisted (moved to the top of the function) and initialised as undefined. Use const and let — they're block-scoped and not hoisted.

console.log(x); // undefined (var hoisted) var x = 5; console.log(y); // ReferenceError (good!) const y = 5;
Learn it: Scrimba → Learn JavaScript (scope)

9 Error handling

Bug: "The app crashes silently and I can't tell why."

Async errors don't bubble up the same way synchronous ones do. Every await call needs a try/catch, or a .catch() handler on the Promise chain.

try { const data = await fetchData(); setState(data); } catch (error) { console.error(error); setError('Failed to load data'); }
Learn it: Scrimba → JavaScript error handling

10 this binding

Bug: "'this' is undefined inside a method I passed as a callback."

In regular functions, this depends on how the function is called, not where it's defined. Arrow functions capture this from the surrounding scope — usually what you want.

// Regular function — this is undefined button.onclick = function() { this.doSomething(); } // Arrow function — this from outer scope button.onclick = () => { this.doSomething(); }
Learn it: Scrimba → Learn JavaScript (this, arrow functions)