These Are The Top 5 Toughest Concepts in JavaScript According To GPT-4
1. Asynchronous Programming
JavaScript is single-threaded, but it has features for asynchronous operations which allow it to handle time-intensive/ compute-intensive tasks (like reading files & making network requests) without blocking the rest of the program.
JavaScript does this with the help of:
- Callback functions
- Promises
- ‘Async/ await’ syntax (built on top of Promises)
To learn Asynchronous Programming, check out these articles from my upcoming book ‘The No Bulls**t Guide To Learning JavaScript’.
2. Event Loop and Concurrency Model
JavaScript has a single-threaded, non-blocking, asynchronous, concurrent runtime, which is primarily implemented through an event loop and a task queue.
Understanding how these work together to handle JavaScript’s execution can be a particularly challenging aspect of the language.
Check out my article which explains this in detail from scratch:
Javascript: Event Loop & Callback Queue Explained!
Event Loop & Callback Queue made easy!
medium.com
3. Closures
A closure gives you access to an outer function or parent function’s scope from an inner/ child function, even after the parent function has finished its execution.
Check out the function below:
function outerFunction(outerVariable) {
return function innerFunction(innerVariable) {
console.log('outerVariable:', outerVariable);
console.log('innerVariable:', innerVariable);
};
};In the above example, innerFunction is a closure.
It is defined inside outerFunction and has access to outerFunction's variables and parameters (outerVariable).
Even after outerFunction has finished execution, innerFunction still retains access to outerVariable through a closure.
When we run newFunction('inside'), which is actually invoking innerFunction, it still has access to outerVariable even though outerFunction has already completed its execution.
const newFunction = outerFunction('outside');
newFunction('inside');
// Output: outerVariable: outside, innerVariable: inside4. Prototype-based Inheritance
In JavaScript, objects inherit properties from other objects.
Every object in JavaScript has an internal property, [[Prototype]], which is a link (reference) to another object.
The [[Prototype]] property of an object is set to the object that was used as its prototype when the object was created.
When we try to access a property that does not exist in the current object, JavaScript will use this link to access the property from its prototype.
If an object’s prototype does not have a desired property, JavaScript will continue looking up the chain, checking the prototype’s prototype, and so on, until it either finds the property or reaches an object with a null prototype.
Let’s create two objects as below.
let animal = {
eats: true,
};
let rabbit = {
jumps: true,
};Next, we set the prototype of rabbit to animal.
rabbit.__proto__ = animal; This can also be done as below(better way):
let animal = {
eats: true
};
let rabbit = Object.create(animal);
rabbit.jumps = true;Now, we can access the properties of the animal object from the rabbit object.
console.log(rabbit.eats);
//Output: trueNote that JavaScript classes, introduced in ES6, is a syntactic sugar over prototypal inheritance. Under the hood, it is the same process of objects inheriting properties and methods from other objects.
5. Hoisting
Hoisting in JavaScript is a mechanism where variable and function declarations are moved to the top of their containing scope during the compilation phase before the code has been executed.
Variable Declarations Are Hoisted & Not Their Initializations
Check out the example below.
console.log(myVar); // undefined
var myVar = 5;
console.log(myVar); // 5In this example, in the first line, accessing myVar returns undefined rather than throwing a ReferenceError.
This is because the variable declaration (var myVar) is hoisted to the top of the scope.
However, the initialization (myVar = 5) is not hoisted.
This looks like the following after hoisting:
var myVar; //Hoised on top at compile time
console.log(myVar); // undefined
myVar = 5;
console.log(myVar); // 5Functions Declarations Are Hoisted & Not Their Initializations
Let’s check out an example.
console.log(myFunction()); // "Hello World!"
function myFunction() {
return "Hello World!";
};This works well because the whole function definition is hoisted to the top when using Function Declaration (with the function keyword).
Let’s see what happens we define a function using Function Expression as below.
console.log(myFunction()); // TypeError: myFunction is not a function
var myFunction = function() {
return "Hello World!";
};In the above, var myFunction is hoisted, but the assignment of the function to myFunction is not. Thus, we get a TypeError when executing the first line.
What about when using ‘let’ and ‘const’?
The let and const keywords in ES6 also hoist their declarations to the top of the block, but accessing them before the declaration results in a ReferenceError.





