🚀 "JavaScript Will Break Your Brain" – 7 Concepts That Confuse Beginners (Until They Don’t)
We’ll break down 7 of the most confusing JavaScript concepts using simple explanations, analogies, and examples.
JavaScript is an incredibly powerful language and it’s everywhere today! But it comes with quirks that confuse beginners and even experienced developers. Concepts like this
, closures, hoisting, and the event loop often lead to unexpected behaviors, making debugging a nightmare. Understanding these tricky parts of JavaScript is like going through levels in a game—you’ll write cleaner, more efficient code and spend less time fixing weird bugs. In this post, we’ll break down 7 of the most confusing JavaScript concepts using simple explanations, analogies, and examples. Whether you’re struggling with scope, asynchronous behavior, or inheritance, this post will try to make them clear in your mind🚀
Too Long to Read? (TLTR)
If you’re short on time, here’s the gist:
this
Keyword: It changes its value depending on how a function is called. Usebind()
,call()
, orapply()
to control it.Closures: Functions that "remember" their parent’s variables. Great for encapsulation and avoiding globals.
Hoisting: JavaScript moves declarations to the top of their scope. Use
let
andconst
to avoid surprises.Event Loop: JavaScript handles asynchronous tasks using a single-threaded event loop. Think of it as a chef managing orders in a kitchen.
Prototypal Inheritance: Objects inherit properties and methods from other objects. JavaScript also supports
class
syntax, but it’s still prototypal under the hood.==
vs===
: Always use===
to avoid type coercion surprises.async/await
: The modern way to handle asynchronous code without falling into callback hell.
Now, let’s dive in more details!
Starting with a quote from everyone who’s ever used JS before:
“JavaScript is easy to learn but hard to master.” – Everyone who’s ever used JavaScript
JavaScript has many quirks that make most beginners confused if not scratching their head. One moment, your code works perfectly. The next, you’re staring at an error message thinking: “Why the hell I’m learning this?”
The truth? Some JavaScript concepts just don’t behave like you expect. But once you understand them, everything becomes clearer.
Here are 7 JavaScript concepts that will confuse you at first—but understanding them will make you a better JS developer.
1. The this
Keyword – “Who Am I?”
Imagine you’re at a party. Someone yells, “Hey, come here!” If you don’t know who they’re talking to, you will be confused!
In JavaScript, this
has the same problem—it depends on how the function is called. Think of this
as a chameleon: it changes its color (value) depending on where it is and how it’s used.
How this
Works?
So, how this
works?
In a regular function (non-strict mode):
this
is the global object (window
in browsers).In a regular function (strict mode):
this
isundefined
.Inside an object method:
this
refers to the object itself.In an arrow function:
this
is “borrowed” from the surrounding scope (lexicalthis
).
Example 1: Regular Function (Confusing Behavior)
Let’s get started with the first example:
function sayHi() {
console.log(this);
}
sayHi(); // 🚨 Logs: window (in browsers, non-strict mode)
In non-strict mode, this
defaults to the global object. But in strict mode, it’s undefined
:
function sayHi() {
"use strict";
console.log(this);
}
sayHi(); // 🚨 Logs: undefined (strict mode)
The function doesn’t belong to any object, so this
behaves differently depending on the mode.
📝 Note: Strict mode is a way to fix JavaScript by allowing developers to opt into a stricter version of JavaScript. It helps you write safer code by catching common mistakes and preventing the use of certain error-prone features. For example:
Assigning to undeclared variables throws an error.
this
in regular functions isundefined
instead of the global object.Deleting variables, functions, or function arguments is not allowed.
To enable strict mode, simply add
"use strict";
at the top of your script or function. It’s like turning on a "safety net" for your code—it helps you avoid pitfalls and write cleaner, more predictable JavaScript code!
Example 2: Object Method (Expected Behavior)
Move on the second example:
const person = {
name: "Boucodes",
greet() {
console.log(this.name);
}
};
person.greet(); // ✅ Logs: "Boucodes"
Here, this
correctly refers to person
because greet()
is called as a method of person
. Think of this
as pointing to the owner of the function.
Example 3: Arrow Functions (Surprising Behavior)
Now, let’s see the third example:
const obj = {
name: "Bob",
greet: () => {
console.log(this.name);
}
};
obj.greet(); // 🚨 Logs: undefined (because arrow functions don’t have their own `this`)
Arrow functions don’t have their own this
. Instead, they inherit this
from the surrounding scope, which in this case is the global scope (or undefined
in strict mode). Think of arrow functions as borrowing this
from their parent.
🚀 Quick Fix: Use
bind()
,call()
, orapply()
when needed. Or just use arrow functions carefully!
2. Closures – JavaScript’s Memory Backpack 🎒
A closure is like a backpack for a function—it carries its parent’s variables wherever it goes. Imagine you’re going on a trip, and you pack a few essentials in your backpack. Even after you leave home, you still have access to those items.
Closures work the same way: they allow a function to “remember” the environment in which it was created, even after that environment is gone.
Why Closures Matter?
✅ Keeps data private (encapsulation).
✅ Avoids global variables.
✅ Helps in real-world scenarios like timers and event listeners.
Example: A Function That Remembers
Let’s see an example of a simple closure:
function makeCounter() {
let count = 0;
return function () {
count++;
console.log(count);
};
}
const counter = makeCounter();
counter(); // 1
counter(); // 2
counter(); // 3
Even though makeCounter()
has finished running, the counter()
function remembers count
. That’s a closure! It’s like the inner function packed count
in its backpack and carried it along.
📖 Want to understand closures? You can read Eloquent JavaScript – Chapter 3 via this link.
3. Hoisting – The JavaScript Magic Trick 🎩✨
The third concept that confuses beginners in JS is hoisting.
Simply put:
“JavaScript reads your code before running it—but only half of it.”
What is Hoisting?
Hoisting is like a magician pulling a rabbit out of a hat. Before the show starts, the magician prepares everything behind the scenes. Similarly, JavaScript “prepares” your code by moving function declarations and variable declarations to the top of their scope before execution.
These are some rules to know:
Function declarations are fully hoisted (you can call them before they’re declared).
Variable declarations (
var
) are hoisted—but without their values.let
andconst
are hoisted but not initialized, leading to a “temporal dead zone.”
Example: Hoisting with var
Let’s begin with a first example:
console.log(name); // 🚨 Logs: undefined
var name = "Alice";
The variable name
is hoisted, but its value isn’t assigned yet. It’s like declaring a box exists but not putting anything in it yet.
Example: Hoisting with let
(Doesn’t Work)
Move on to the next example:
console.log(age); // ❌ ReferenceError
let age = 25;
With let
and const
, the variable exists but is in a “temporal dead zone” until it’s initialized.
🚀 Best Practice: Always try to declare variables at the top of your scope to avoid confusion.
4. The Event Loop – Why JavaScript Feels “Slow” 🌀
Now, let’s see about the famous event loop. JavaScript is single-threaded, meaning it can only do one thing at a time. But how does it handle multiple tasks like API calls, timeouts, and user clicks?
💡 Think of JavaScript as a chef in a small kitchen.
The chef (main thread) can only cook one meal at a time.
Orders (callbacks like
setTimeout
) go into a waiting queue.The event loop checks when the chef is free and sends in the next order.
Example: Why setTimeout(0)
Isn’t Immediate?
Let’s start with a common confusion related to how the event loop works:
console.log("Start");
setTimeout(() => {
console.log("Timeout");
}, 0);
console.log("End");
This is the expected output by most people:
Start
Timeout
End
But the actual output is as follows:
Start
End
Timeout
Why? Because setTimeout
goes to the queue, and JavaScript finishes everything in the main thread first. It’s like the chef finishing the current meal before starting the next one.
📖 You can watch this video for in-depth explanation mind-blowing Event Loop video.
5. Prototypal Inheritance – Objects That Borrow Traits 🏛
Unlike other languages that use class-based inheritance, JavaScript uses prototypal inheritance.
💡 Think of it like borrowing skills from your ancestors. If you don’t know how to do something, you ask your parents. If they don’t know, they ask their parents if they are still alive :), and so on.
Example: Object Inheriting From Another Object
Let’s see the following example:
const person = {
greet() {
console.log("Hello!");
}
};
const user = Object.create(person);
user.greet(); // ✅ Logs: "Hello!"
Even though user
doesn’t have greet()
, it inherits it from person
!
📝 Note: Please note that while JavaScript is fundamentally prototypal, it added support for a
class
syntax with the introduction of ES6 for developers familiar with class-based languages like Java or C++. Under the hood, however, classes in JavaScript are still based on prototypes.
📖 Read: You Don’t Know JS: Objects & Prototypes
6. ==
vs ===
– The Identity Crisis 🤔
JavaScript sometimes tries to be “helpful” by converting types in ==
comparisons.
Example: Why Type Coercion is Dangerous?
Let’s see this example demonstrating why coercion is dangerous:
console.log(5 == "5"); // ✅ true (JavaScript converts "5" into a number)
console.log(5 === "5"); // ❌ false (Strict comparison, different types)
The difference between ==
and ===
is commonly asked during interviews so make sure to remember it!
🚀 Best Practice: Try to always use
===
to avoid weird behavior.
7. async/await
– The Fix for Callback Hell 😵💫
This is actually a nice feature that’s not confusing at all but always beginners ask why we need them so I included them in this list!
Before async/await
, we had callback hell:
getUser(1, function(user) {
getPosts(user.id, function(posts) {
getComments(posts[0], function(comments) {
console.log(comments);
});
});
});
Then Promises made it better:
getUser(1)
.then(user => getPosts(user.id))
.then(posts => getComments(posts[0]))
.then(console.log);
But async/await made it BEAUTIFUL:
async function fetchData() {
let user = await getUser(1);
let posts = await getPosts(user.id);
let comments = await getComments(posts[0]);
console.log(comments);
}
fetchData();
📖 You can read this guide for an extra explanation: MDN’s Async/Await Guide
Final Thoughts
JavaScript can be weird, but once you understand these concepts, you’ll become a better developer.
💡 Best Free JavaScript Learning Resources:
📖 Eloquent JavaScript
📖 You Don’t Know JS
📖 JavaScript.info
🚀 Which JavaScript concept used to confuse you the most? (I hope it doesn’t anymore) Drop a comment below! 👇 And if you learned something from this article, leaving a like is appreciated!