JavaScript Core Concepts – this, bind, loops, generators, and more
A curated collection of essential JavaScript concepts often asked about in interviews and critical for writing robust code. Each topic is backed by practical examples, real-world context, and explanations that help you understand how and why things work — not just memorize syntax.
Understanding this
Below users.customFilterNoThis(army.canJoin)
, throws, army.canJoin
was called
as a standalone function, with this=undefined
, thus leading to an instant error.
A call to users.customFilter(army.canJoin, army)
can be replaced with
users.customFilterNoThis(user => army.canJoin(user))
, that does the same.
This is an object before dot
Array.prototype.customFilter = function (callback, thisArg) {
let result = [];
for (let i = 0; i < this.length; i++) {
if (callback.call(thisArg, this[i], i, this)) {
result.push(this[i]);
}
}
return result;
};
Array.prototype.customFilterNoThis = function (callback) {
let result = [];
for (let i = 0; i < this.length; i++) {
if (callback(this[i], i, this)) {
result.push(this[i]);
}
}
return result;
};
const army = {
minAge: 18,
maxAge: 27,
canJoin(user) {
return user.age >= this.minAge && user.age < this.maxAge;
},
};
const users = [{ age: 16 }, { age: 20 }, { age: 23 }, { age: 30 }];
const soldiers1 = users.customFilterNoThis(army.canJoin);
// return user.age >= this.minAge && user.age < this.maxAge;
// TypeError: Cannot read properties of undefined (reading 'minAge')
const soldiers2 = users.customFilterNoThis((user) => army.canJoin(user));
const soldiers3 = users.customFilter(army.canJoin, army);
console.log(soldiers2); // [ { age: 20 }, { age: 23 } ]
console.log(soldiers3); // [ { age: 20 }, { age: 23 } ]
Loop Behavior: Pre/Post Increment
This example highlights the subtle differences between ++i
(pre-increment) and i++
(post-increment) in while
loops, and how they affect loop behavior and output.
It also compares for
loops using i++
vs ++i
, which behave the same in this context, since the increment happens after the loop body executes.
let i = 0;
while (++i < 3) console.log(i);
// 1, 2
let i2 = 0;
while (i2++ < 3) console.log(i2);
// 1, 2, 3
for (let i = 0; i < 3; i++) console.log(i);
// 0, 1, 2
for (let i = 0; i < 3; ++i) console.log(i);
// 0, 1, 2
Object and Map Conversion
This example shows how to convert between plain JavaScript objects and Map
instances using Object.entries()
and Object.fromEntries()
.
These methods are useful when working with APIs or libraries that prefer one format over the other, or when you need key ordering and additional Map
features.
const prices = Object.fromEntries([
["banana", 1],
["orange", 2],
["meat", 4],
]);
console.log(prices);
// { banana: 1, orange: 2, meat: 4 }
const map = new Map();
map.set("banana", 1);
map.set("orange", 2);
map.set("meat", 4);
const arrayLikeMapEntries = map.entries();
const arrayMapEntries = Array.from(arrayLikeMapEntries);
const objectFromMap = Object.fromEntries(arrayMapEntries);
console.log(objectFromMap);
// { banana: 1, orange: 2, meat: 4 }
const mapFromObject = new Map(Object.entries(objectFromMap));
console.log(mapFromObject.get("meat")); // 4
bind
This example demonstrates how the bind
method is used to fix the this
context of a function.
In JavaScript, methods like setTimeout
lose the original object context when passing a function reference.
Using bind
, we can permanently associate a method with its object (user
), ensuring this.firstName
refers to the correct value.
This is useful when passing object methods as callbacks, especially in asynchronous operations.
const user = {
firstName: "John",
sayHi() {
console.log(`Hello, ${this.firstName}!`);
},
};
user.sayHi(); // Hello, John!
setTimeout(user.sayHi, 0); // Hello, undefined!
// solution 1
setTimeout(function () {
user.sayHi(); // Hello, John!
}, 0);
// or shorter
setTimeout(() => user.sayHi(), 0); // Hello, John!
// solution 2
const sayHi = user.sayHi.bind(user);
// can run it without an object
sayHi(); // Hello, John!
setTimeout(sayHi, 0); // Hello, John!
Async Generators
This example demonstrates how to use an async function*
(asynchronous generator) to yield values over time with a delay.
The generateSequence
function yields numbers from start
to end
, waiting one second between each using await
.
The for await...of
loop is used to consume the generator asynchronously, making it perfect for streaming data,
timers, or controlled iteration over async events.
async function* generateSequence(start, end) {
for (let i = start; i <= end; i++) {
await new Promise((resolve) => setTimeout(resolve, 1000));
yield i;
}
}
const timer = async (callback) => {
const generator = generateSequence(1, 5);
for await (let value of generator) {
callback(value);
}
};
timer(console.log).catch(console.error);
// 1, 2, 3, 4, 5 (with delay between)
Fibonacci
The Fibonacci sequence is a series where each number is the sum of the two preceding ones:
1, 1, 2, 3, 5, 8, 13, 21, ...
This example shows two ways to calculate the n
-th Fibonacci number:
🔁 Iterative Approach
Efficient and suitable for large values of n
(like 77). Uses a loop and keeps track of the last two values.
function fib(n) {
let a = 1;
let b = 1;
for (let i = 3; i <= n; i++) {
let c = a + b;
a = b;
b = c;
}
return b;
}
console.log(fib(3)); // 2
console.log(fib(7)); // 13
console.log(fib(77)); // 5527939700884757
🔁 Recursive Approach
Elegant but inefficient for large n
due to repeated calculations (exponential time complexity). Best for learning recursion.
function fib(n) {
return n <= 1 ? n : fib(n - 1) + fib(n - 2);
}
console.log(fib(3)); // 2
console.log(fib(7)); // 13
console.log(fib(77)); // -