Qs: JavaScript
TODO
This doc is alive, hence this TODO section :)
For each topic: What / Why / When(realusecases) / How(code) Analogy + Mental Models + Memory Tip
Promise static methods
What's a pure function in JavaScript?
When do we use apply()?
Why?
Still relevant?
When do we use bind()
Why?
Still relevant?
diff between undefined and null
let vs const vs var
Classes
Classes
Classes vs Objects
Objects
Classes Accessibility
Classes Constructor
Functions vs Objects
Primitives
Reference Types
interpreter vs compiler
ES6 modules .mjs or with "type": "module" in package.json
One-Liners Every JS Dev Should Know
JS Core Concepts
Variable – Named storage -
let
,const
,var
.Scope – Defines where
variables
/functions
are accessible -block
,function
,global
.Hoisting – JS moves
variable
/function
declarations to the top of their scope.Closure – A function that “remembers” variables from its outer scope.
Lexical Scope –
Scope
determined by where code is written, not where it's executed.Execution Context – The environment in which code is evaluated (
this
,variables
,functions
).Call Stack – Stack structure that tracks
function calls
.Event Loop – JS mechanism that handles
async
tasks -callbacks
,promises
.Garbage Collection –
Automatic memory management
by JS engine.Strict Mode ('use strict') – Enables stricter parsing & error handling.
JS Data Types & Structures
7 Primitive Types –
string
,number
,bigint
,boolean
,null
,undefined
,symbol
.Reference Types –
Object
,Array
,Function
, etc.NaN (Not a Number) – Special value for invalid number operations.
Falsy Values –
false
,0
,""
,null
,undefined
,NaN
.JSON (JavaScript Object Notation) – Lightweight data format.
Spread Operator (
...
) –Expands
elementsfrom
arrays
,objects
.Rest Parameters (
...args
) –Collects
argumentsinto
anarray
.Destructuring –
Extract
values fromarrays
/objects
.Map / Set – Modern collections with unique keys/values.
Prototype – Mechanism for inheritance in JS objects.
JS Functions & OOP
First-class Functions – Functions treated like values.
Higher-order Functions – Functions that take/return functions - Wrapper / Transformator / Decorator / Intersector.
Arrow Functions
()=>
– Shorter syntax, nothis
binding - no need forself=this
hack in closures.Callback – A
function
passed as anargument
.Constructor Function –
Function used with new
to create objects.Class –
ES6 syntax sugar
for OOP."This" Keyword – Refers to the
current execution context
.Bind / Call / Apply – Methods to control
this
context.Encapsulation – Grouping related state and behavior.
Polymorphism – Different forms of the same method in inheritance.
JS Asynchronous & Events
Callback Hell –
Nested callbacks
, making code messy.Promise – Object representing
async completion/failure
.Async / Await – Syntactic sugar for
promises
.Microtask Queue – Where
promises
are queued - faster thanmacrotasks
.Macrotask Queue – Queue for
setTimeout
,setInterval
, etc.Event Bubbling – Events propagate from
child
→parent
.Event Capturing – Events propagate from
parent
→child
.Event Delegation – Handling events at a
higher level
for efficiency.Debouncing –
Delays
execution until after apause
.Throttling –
Limits
execution to once perinterval
.
Getting Started
What's ES5, ES6, ES7 about?
ES
stands for ECMAScript
.
JavaScript is standardized by ECMAScript
.
Each new version adds features, fixes, and improvements.
ES5 (2009): Strict mode, array methods, JSON.
ES6 (2015): Big upgrade (let/const, arrow functions, classes, modules, promises).
ES7+ (2016 onward): Smaller yearly updates (async/await, optional chaining, BigInt, etc.).
How are ECMAScript versions decided?
The TC39 group is responsible for evolving JavaScript (officially, ECMAScript).
TC39 = Technical Committee 39.
TC39 is part of Ecma International, the standards organization that manages the ECMAScript specification.
Members are representatives from big tech companies (Google, Microsoft, Apple, Mozilla, Intel, etc.) and independent experts.
What does ESM stand for?
ESM = ECMAScript Modules = ECMAScript 2015 (ES6) Modules
Since ECMAScript 2015 (ES6), javascript supports built-in modules through the new syntax.
JavaScript did not have built-in support for modules prior to 2015. We had to use other module systems, i.e. AMD and CommonJS
What's "use strict" ?
It is a directive that enables strict mode
.
Strict mode
changes the way JavaScript executes by enforcing stricter parsing and error handling.
Introduced with ES5 (2009).
Why "use strict" ?
It helps catch common coding mistakes and prevents potentially unsafe actions.
Makes the code: safer, more predictable, and easier to debug.
this
is not automatically bound to window - NO implicit globalthis
Variables must be declared
Assignment to read-only properties throws errors
No duplicate parameter names in functions
Reserved keywords are protected i.e. implements, interface, let, package, private
When "use strict" in real-life use-cases?
Most of the time, "use strict"; is recommended because it makes your code safer.
Few rare cases where you might avoid it:
Old JavaScript Libraries - some were written assuming
sloppy mode
(especially those pre-ES5
, pre-2009)Legacy Codebases
What is "sloppy mode" ?
sloppy mode
= non-strict mode
= opposite of strict mode
This means:
this
is automatically bound to window - implicit globalthis
Variables do not have to be declared
Assignment to read-only properties does NOT throw errors - it fails silently
Duplicate parameter names are allowed in functions
Not reserved keywords i.e. you can call your variable
public
Declared vs Created in JS ?
let x; // declared
x = 5; // created in memory with value 5
var y; // declared
const z = 10; // declared and initialized
Declaration = A variable is declared when you introduce its name in the code using var, let, or const.
Declaration = telling JS “this name exists”.
Creation = JS allocates memory and stores the value.
Declaration:
tells JS: “Hey, reserve a spot for this variable.”
No memory for actual value is allocated yet (for some cases) until it’s initialized.
Declaration alone does not mean it has a value (except for const, which must be initialized).
Creation:
For primitives: creation = storing the value in the stack.
For objects: creation = allocating memory in the heap + storing reference in stack.
Created vs Called in JS Functions ?
Created (Function Creation / Definition)
JavaScript allocates memory for it, i.e., the function object is created in memory and assigned to a reference (the function name).
function greet() {
return "Hello!";
}
Called (Function Invocation / Execution)
JavaScript runs the code inside the function body.
JavaScript creates an execution context for the function.
Any local variables inside the function are created in the stack.
When execution finishes, local variables are popped from the stack.
greet(); // calling the function
What's hoisting?
The process whereby the interpreter appears to move the declaration of functions
, variables
, classes
, or imports
to the top of their scope, prior to execution of the code.
Functions and Variables are hoisted.
var
Yes
No
No
Yes, value = undefined
let
/ const
Yes
No
Yes
No (ReferenceError)
Function declaration
Yes
Yes
No
Yes
Function expression
Variable only (var
/let
/const
)
No
Yes for let
/const
No (depends on type)
What's variable hoisting?
TODO
What's function hoisting?
Function hoisting means that functions are moved to the top of their scope. That is,
function b() {
a = 10;
return;
function a() {}
}
will be rewritten by the interpeter to this
function b() {
function a() {}
a = 10;
return;
}
Can you do multithreading in JavaScript?
No. Not natively.
JS in majority of browsers run on V8
.
HTML5 Web Workers define a way to run script in the background. You can then execute some tasks in threads living outside the main page and thus non-impacting the drawing performance.
https://learn.microsoft.com/en-us/previous-versions/msdn10/hh549259(v=msdn.10)
NODE.JS SPECIFIC
JS in majority of browsers run on V8, which is essentially same engine used by Node.js
The very foundation of Node.js and the way it works is it has Event Loop on single thread - which is intentional. It removes any issues with synchronizations between threads and any overhead with creating/deleting threads. That is very important concept for backend as it often handles tens or hundreds of concurrent requests at once.
It makes also code very deterministic (you know that the synchronous part of the code taken by Event Loop will be executed fully before another event in Event Loop will be taken and processed)
However from the beginning - Node.js HAS thread pool. Another threads are used for I/O operations in background (like requests or handling files). You can create workers which does have their own threads if you like to.
Does the core of Node.js change? Most likely not, it would be breaking change and it would be actually against the main idea of Node.js. Also not bringing much benefits (as you can use more threads if you really want to already)
How Do You Improve Code Quality?
Stick to best practices and industry standards:
use
ESLint
andPrettier
.automated testing with
Jest
andCypress
.considering
SonarQube
for static code analysis (automated code reviews) - to detect bugs, code smells, vulnerabilities, duplications, and security hotspots.code reviews
(manual code reviews) - points above reduce time required for "manual work"CI/CD
This increases code maintainability, reduces in-production issues, and reduces rollbacks.
What is ESLint?
ESLint is a configurable JavaScript linter. It helps you find and fix problems in your JavaScript code.
Problems can be anything from potential runtime bugs, to not following best practices, to styling issues.
What is Prettier?
TODO
How do you find unused dependencies?
Use tools like
depcheck
to see if you have any unused library in your package.json
In general, webpack should be removing those with tree shaking anyhow, and dead code remove but still you want to keep it clean.
Use the
webpack bundle analyser
to see the JavaScript cost of adding each library to evaluate if any library should be replaced with a lighter implementation
Some libraries you will be able to easily remove, some others are harder to replace (if the codebase use them heavily).
The important thing is you make those decisions (adding a new library to the codebase for example) with performance in mind, rather than keep adding dependencies to a project (pretty common in the JS world).
Scope, Context and "this"
What's the difference between Context vs Scope in Javascript?
Context and scope are related but very different concepts. People often confuse them.
1. Scope (Lexical)
TLDR: Scope = variable access.
Definition: Scope refers to the current set of variables available
at a specific point in your code.
Determined by: set at write time (lexical structure) - how and where code is written (lexical environment) - cannot be changed at runtime.
function outer() {
let a = 10; // function scope
if (true) {
let b = 20; // block scope
console.log(a); // 10 (accessible inside nested scope)
}
console.log(b); // ReferenceError (block-scoped)
}
outer();
2. Context (this)
TLDR: Context = object owning the current execution (this
).
Definition: Context refers to the value of this
within a piece of code.
Determined by: set at runtime by how a function is invoked/called - it can be changed at runtime with .call()
, .apply()
, .bind()
.
const obj = {
value: 42,
regularFn: function () {
console.log(this.value); // depends on how it's called
},
arrowFn: () => {
console.log(this.value); // arrow functions don't have their own `this`
}
};
obj.regularFn(); // 42 (context is `obj`)
obj.arrowFn(); // undefined (context comes from outer scope, usually `window` in browsers)
Comparison Table
Aspect
Scope (Lexical)
Context (this
)
What?
Variables available to use
The object that "owns" the execution
When set?
At write time (lexical structure)
At call time (invocation)
Changes?
Cannot be changed at runtime
Can change with .call()
, .apply()
, .bind()
Example
{ let x = 1; }
→ x
is scoped
obj.method()
→ context is obj
Scope = what variables you can access.
Context = what this
refers to when running a function.
What Are The 5 Different Scopes In JavaScript?
Global Scope → Variables accessible everywhere – variables declared outside any function/block.
Function Scope → Variables created inside functions (var).
Block Scope → Variables created by
{}
withlet
andconst
.Lexical Scope
(Static Scope) → how closures work - Inner functions access variables of Outer Function.Module Scope (ES6 Modules) → Variables private to modules unless exported.
What's Lexical Scope?
Lexical scoping = how closures work.
Inner functions access variables of outer function.
Functions remember the scope in which they were defined, no matter where they’re later called.
Was Lexical Scope
introduced by arrow functions
with ES6 (2015) ?
Lexical Scope
introduced by arrow functions
with ES6 (2015) ?No. Lexical scoping is in JavaScript since its very first version in 1995. But it improved lexical scoping
for arguments and closures.
ES6 (2015) introduced new ways of declaring variables (let
and const
) that are block-scoped
:
Lexical scoping = how closures work, and it has always been part of JavaScript. Functions remember the scope in which they were defined, no matter where they’re later called.
var
(pre-ES6) =function-scoped
, which sometimes made closures trickier (like infor loops
).let
andconst
(ES6) =block-scoped
, which made closures more intuitive in certain cases.
Use Cases for Lexical Scope
Closures (data privacy) - private variables - "remember" variables from their defining scope, even after that scope is gone
Callbacks in Asynchronous Code - retain access to variables; useful in timers, promises, and async/await
Arrow Functions Preserving
this
- fixes thethis
binding problem.Functional Programming (map, filter, reduce) - keeps variables accessible in inline functions
Event Handlers - reference outside variables safely
Currying & Partial Application - pre-fill arguments using
closures
Module Pattern (Data Encapsulation) -
Lexical scope
+closures
= simulateprivate
fields before ES6 classes.Memoization (Caching Results) - holds a cache between calls
IIFE (Immediately Invoked Function Expressions) - IIFEs create a lexical scope to avoid polluting globals.
What's the informal "Object Scope" about?
Object Scope
isn’t a formal type of scope likeglobal
,function
, orblock
scope.Object Scope
mainly refers to accessing and controllingproperties
/methods
viathis
and references.regular methods
vsarrow methods
is a hack to access different scopes viathis
this
insideregular methods
points to the object.this
insidearrow methods
points to the surroundinglexical scope
.
True
private
variables in objects requireclosures
.Object properties are not variables—they don’t follow var/let/const scoping.
Function Scope And The Informal Object Scope
Explain these points:
Function Scope
: differences betweenarrow functions
VSregular functions
The
this
binding problem forregular functions
- it's inconsistent/dynamicWhy were
arrow functions
introduced when we already hadregular functions
The informal
object scope
, and howarrow functions
andregular functions
relate to it
Args, Rest, Spread
What are the difference between rest
and spread
syntaxes ?
rest
and spread
syntaxes ?Rest
and Spread
look the same (...) but they are kind of the opposite:
Rest
= Pack = Collects values into an array.Spread
= Unpack = Expands an array or object into individual values.
Why Use rest
vs spread
Syntaxes ?
rest
vs spread
Syntaxes ?Rest
syntax usage: infunction parameters
,object destructuring
, orarray destructuring
.Spread
syntax usage: infunction calls
,array literals
, orobject literals
.
Why Use rest
vs spread
Syntaxes get confusing when used on functions ?
rest
vs spread
Syntaxes get confusing when used on functions ?Because one is for function definitions and the other is for functions calls.
Rest syntax -> function definitions (it has "{...}"), that's when we call it rest parameter
.
Spread syntax -> function call, that's when we call it spread syntax
.
Rest parameter
function f(...args) {}
Function parameter list (function definition)
Collects remaining arguments into an array
Spread syntax
f(...array)
Function call, array/object literal
Expands an array or object into individual elements
Function Declaration
function greet() {}
Declares how a function works
Function Expression
const greet = function() {};
Defines a function inside an expression
Arrow Function
const greet = () => {};
Defines a shorter function
Function Call
greet();
Executes a function
Why Use rest
Syntax ?
rest
Syntax ?Rest parameter
function f(...args) {}
Collects remaining arguments (function inputs) into an array
Object rest
const { a, ...rest } = obj;
Collects remaining object properties into a new object
Array rest
const [x, ...rest] = arr;
Collects remaining array elements into a new array
Examples For rest
Parameters Usage ?
rest
Parameters Usage ?Only and only if:
in function parameter lists
in function definition (not when it's called)
function logAll(...args) {} // Function declaration
const logAll = function(...args) {}; // Function expression
const logAll = (...args) => {}; // Arrow function
class Logger { log(...messages) {} } // Class method
function greet(greeting, ...names) { // Combine Named and Rest Parameters
names.forEach(name => console.log(`${greeting}, ${name}!`))
}
greet("Hello", "Alice", "Bob", "Charlie"); // will print 3 lines: "Hello, Alice!" then "Hello, Bob!" then "Hello, Charlie!"
function multiply(a, b) { return a * b; }
function logAndMultiply(...args) { // function definition so: rest syntax, rest parameters
console.log("Multiplying:", args);
return multiply(...args); // function call so: spread syntax
}
console.log(logAndMultiply(3, 5)); // 15
When using rest parameter (...args in function), what will be the value of ...args
if no parameters is passed?
...args
if no parameters is passed?An empty array.
function demo(...args) {
console.log(args);
console.log(Array.isArray(args));
}
demo(); // Output: [] true
demo(1, 2, 3); // Output: [1, 2, 3] true
→ code above shows that args
is always an array
How to use rest
or spread
syntaxes together ?
rest
or spread
syntaxes together ? function logAndCall(fn, ...args) { // rest syntax
console.log("Calling with: ", args);
console.log(fn(...args)); // spread syntax
}
logAndCall(Math.max, 1, 5, 3); // output below
// Calling with: [1, 5, 3]
// 5
First the rest
syntax first collects extra arguments into an array.
Then the spread
syntax spreads the array back into individual arguments.
This pattern allows you to write generic wrappers for any function (c.f. HOFs)
What is ...args about ?
...args
= rest
syntax = argument collector.
Collects all arguments passed to a function
into a single array
.
function foo(...args) {
console.log(args);
}
foo(1, "a", true); // logs: [1, "a", true]
Why use ...args ?
Makes functions flexible, concise, and future-proof.
Flexible → Handle any number of arguments
Cleaner → Avoid manually accessing
arguments
- directly use map, filter, reduce, etc.Powerful → Works well with higher-order functions - write generic functions that forward arguments to other functions
Readable → Makes code cleaner and more readable
When, in which real-life use-case
TODO
How to use ...args ?
...args
must be the last parameter in the function.
Advanced usage of ...args ?
Example with nested functions with each its own ...args
In nested function, args
is different and local. They don’t overwrite each other.
Can you use ...args in regular functions?
Yes — you can use rest parameters ...args
with regular functions AND arrow functions.
...args
was introduced in ES6 (2015).
Before ES6, what did you use instead of ...args?
We could use the special arguments
object.
arguments
is NOT a real array (just array-like) -> you CANNOT DIRECTLY use array methods such as.map
,.reduce
- prior conversion required.arguments
does NOT work in arrow functions - ONLY in regular functions.
For the history:
...args
came in ES6 and replaces the olderarguments
object:...args
is a real array, so you can use array methods right away....args
works in both regular and arrow functions.
Why were arrow functions introduced?
Arrow functions were introduced in ES6 (ECMAScript 2015) primarily to:
Shorter and Cleaner syntax.
Fix the common
this
binding problem.Improve
lexical scoping
for arguments and closures.Encourage functional programming patterns.
Prevent misuse as constructors.
Fix the common this
binding problem
this
binding problemOne of the most common pitfalls in JavaScript was that this
was inconsistent/dynamic. It changed depending on how a function was called:
function Person() {
this.age = 0;
setInterval(function() {
this.age++; // "this" is not the Person object
}, 1000);
}
Solutions before ES6 required hacks: var self = this;
or bind(this)
.
const person = {
name: "Alice",
greet: function() {
const self = this; // `self = this` hack
setTimeout(function() {
console.log(self.name); // prints "Alice"
}, 1000);
}
};
Arrow functions capture this
from their surrounding scope, so you don’t need extra hacks:
function Person() {
this.age = 0;
setInterval(() => {
this.age++; // 'this' is the Person instance
}, 1000);
}
Implicit Objects and Variables
What are Implicit Objects and Variables ?
This refers to the automatically available objects and variables that the JavaScript runtime provides, without the programmer explicitly declaring or importing them.
These come from the execution context (global
, function
, or block
scope), and they depend on where and how the code is executed.
Examples:
Global Object
window
/ global
/ globalThis
Global environment container
Function Context
this
Refers to current execution context
Function Context
arguments
Holds passed arguments (non-arrow)
Variable
Undeclared variables
Implicitly global (discouraged)
Return Value
undefined
Default return when none is specified
Implicit (Automatically Available) Objects
window
/global
/globalThis
: global objects that act as containers for all global variables and functions.
In browsers: The global object is
window
.console.log(window === this);
// true in global scopeIn Node.js: The global object is
global
.console.log(global === this);
// false in Node's module scopeUniversal (ES2020+):
globalThis
works in any JS environment.
this
: implicit object reference automatically available insidefunctions
andobjects
.arguments
: inside anynon-arrow function
, contains all passed parameters. Note thatargs
is preferred in modern js bc cleaner, safer, and works everywhere.
Memory, Reference Types and Primitives
How does the memory work in JavaScript ?
JavaScript uses two main memory regions:
Stack → Fast, small storage for simple data (
primitives
).Heap → Large, flexible storage for complex data (
objects
,arrays
,functions
).
Reference types (objects, arrays, functions) live in the heap
, and variables store a pointer (reference) to them in the stack
.
That’s why when you copy a reference type, you don’t copy the value itself, but the reference (so changes affect both).
Code example illustrating how the memory work in JavaScript ?
let num = 42; // primitive → stack
let obj1 = { name: "Alice" }; // reference → stack holds pointer, heap stores actual object
let obj2 = obj1;
obj2.name = "Bob";
console.log(obj1.name); // Output: "Bob"
Memory model:
Stack:
num → 42
obj1 → (ref) 0x001
obj2 → (ref) 0x001
Heap:
0x001 → { name: "Bob" }
Are Stack and Heap are different type of hardware ?
This is a very common confusion.
The stack and heap in programming are NOT different pieces of physical hardware.
Conceptual differences, not hardware differences.
They are two regions of memory managed differently by the runtime (in our case, the JavaScript engine, like V8 in Chrome).
Stack:
Think of it as a neat pile of plates — last in, first out.
Stores simple, fixed-size data (like numbers, booleans, and references to objects).
Memory is automatically managed — when a function ends, all its local variables are popped off.
Very fast because it’s just pushing/popping in a known order.
Heap:
Think of it as a messy storage room — you can put things anywhere, but need to keep track of where.
Stores complex, dynamic data (like objects, arrays, functions).
Memory allocation is flexible but slower than stack.
The JavaScript garbage collector cleans unused objects.
Which Data Is Of Primitive Types?
Primitive type is data that is not an object.
SEVEN primitive types:
number
string
boolean
null
undefined
symbol
bigint
What are Reference Types?
All non-primitive types
are reference types, with Object being the root
-> All objects are reference types -> And all reference types are objects.
Main Reference Types:
Object
{}
Base for most structures
Array
[]
Ordered list
Function
function(){}
Callable object
Date
new Date()
Date/time
RegExp
/pattern/
String matching
Map
new Map()
Key–value pairs, keys can be any type
Set
new Set()
Unique values
WeakMap
new WeakMap()
Keys must be objects, garbage-collectable
WeakSet
new WeakSet()
Stores objects only, garbage-collectable
Error
new Error()
Error handling
JavaScript Language Is Pass-By-Value Or Pass-By-Reference ?
JavaScript is a Pass-By-Value for primitives, and Pass-By-Reference for reference types.
When a function
is called, a copy of the value
of each argument
is passed to the function
, NOT the original argument
.
This means that the function
cannot modify the original arguments. Modifications done to any argument
the function
received only have effect inside the function
.
Primitives
→ Passed byValue
→ stored in StackObjects
→ Passed by Reference to Value (reference itself is copied) → Object stored in Heap, Reference stored in Stack.
JavaScript always passes function arguments by value
.
But when that value
is a reference
(for objects
/arrays
/functions
), the reference itself is copied, so both variables point to the same underlying object.
What is a Symbol?
Primitive data types introduced in ES6.
A Symbol is a unique
and immutable
identifier.
Even if two symbols have the same description, they are always different values.
Why use Symbol?
Often used as object property keys to:
avoid naming conflicts
create hidden properties
Symbol is often used to define unique constants.
JavaScript also uses some well-known symbols internally (Symbol.iterator, Symbol.toStringTag, etc.).
How to use Symbol with code examples?
Example 1 – Uniqueness
const a = Symbol("id");
const b = Symbol("id");
console.log(a === b); // false (even if descriptions are the same)
Example 2 – Using as Object Keys
// Define a symbol for private/hidden data
const secretId = Symbol("secretId");
const user = {
name: "Alice",
age: 25,
[secretId]: 98765 // hidden property
};
// Normal access
console.log(user.name); // Output: "Alice"
console.log(user.age); // Output: 25
console.log(user.secretId); // Output: undefined
// Access using the symbol
console.log(user[secretId]); // Output: 98765
// Iterating over keys/properties
for (let key in user) { // Output: name, age
console.log(key);
}
console.log(Object.getOwnPropertySymbols(user)); // Output: [ Symbol(secretId) ]
Browser API
Browser API: familiar with different authentication and storage mechanisms present in the browser?
Cookies, local storage, and session storage.
Browser API: familiar with the History API?
Yes, used by all modern frameworks to mimic HTTP-like routing in SPAs (Single Page Applications).
Functions
What does "functions are first class objects" mean?
All the function declarations are eventually assigned to a variable
.
That means they can be passed to other functions
, be stored in arrays
, and so on.
What is func.apply(this, args)
?
func.apply(this, args)
?It uses the Function.prototype.apply()
method to call a function (func
) with:
this
→ the value that will be bound as this insidefunc
.args
→ an array (or array-like object) of arguments to pass to func
function greet(greeting, name) {
console.log(greeting + ', ' + name);
}
const args = ['Hello', 'Alice'];
greet.apply(this, args); // Output: "Hello, Alice"
When to use Function.prototype.apply()
method?
Function.prototype.apply()
method?If you want to control what this
points to inside the function.
// Example without .apply()
function showName() { console.log(this.name); }
const person1 = { name: "Alice" };
const person2 = { name: "Bob" };
person1.say = showName; // Assign function
person1.say(); // "Alice"
showName(); // undefined (or window/global in non-strict mode)
// Example with .apply()
function showName() {
console.log(this.name);
}
const person1 = { name: "Alice" };
const person2 = { name: "Bob" };
showName.apply(person1); // "Alice"
showName.apply(person2); // "Bob"
Difference between Function.prototype.apply()
and Function.prototype.call()
methods?
Function.prototype.apply()
and Function.prototype.call()
methods?.apply(thisArg, argsArray)
→ calls the function with a chosen this
and arguments.
.call(thisArg, ...args)
→ same as .apply()
, but arguments are passed individually.
Difference between Function.prototype.bind()
and call()
and apply()
methods?
Function.prototype.bind()
and call()
and apply()
methods?call
/ apply
→ when you want to invoke the function right now with a specific this
.
bind
→ when you want a function you can call later, but with this
(and maybe some args
) already “locked in”.
What and How to use Function.prototype.bind
?
Function.prototype.bind
?bind
→ used to create a new function
with a fixed this value
.
const obj = { x: 10 };
function printX() { console.log(this.x); } // function definition
const boundFn = printX.bind(obj); // bind "obj" to the "this" of "printX" function (hence object)
boundFn(); // output: 10
What javascript pattern is used in the following code snippet?
(function (module) {
const someVariable = "someVar";
function returnFirstChar(text) {
// ...
}
module.returnFirstChar = returnFirstChar;
module.firstChar = firstChar;
window.myModule = module;
})({});
An IIFE (Immediately Invoked Function Expression) used as a module declaration.
Classes, Prototypes, Objects
What are prototypes in javascript?
Every JavaScript object has an internal link to another object called its prototype.
That prototype object can have its own prototype, and so on, forming a prototype chain.
When you try to access a property on an object, JavaScript looks for it:
On the object itself.
If not found, it looks up the prototype chain until it reaches Object.prototype.
ES6
class
is syntactic sugar over prototype-based inheritance.Objects inherit from other objects via prototypes.
Constructor functions use
prototype
for shared methods.Object.create(proto)
creates a new object with the given prototype.An object’s internal Prototype is accessible via
__proto__
How: Code Example for a Basic Prototype
const person = {
greet() {
console.log("Hello!");
}
};
const user = Object.create(person); // user’s prototype is person
user.greet(); // Inherited from person → "Hello!"
Here, user
doesn’t have greet
, but it inherits it from its prototype
(person
).
Why Use Function.prototype?
Because we want all objects created by the same constructor to share methods instead of duplicating them.
Person.prototype.sayHello = function() {
console.log("Hi, I'm " + this.name);
};
const alice = new Person("Alice");
const bob = new Person("Bob");
alice.sayHello(); // "Hi, I'm Alice"
bob.sayHello(); // "Hi, I'm Bob"
Both alice and bob share the same sayHello
function on Person.prototype
.
This saves memory and allows consistent behavior across instances.
Prototype chain for an object like alice created from Person:
alice → inherits from
Person.prototype
Person.prototype
→ inherits fromObject.prototype
Object.prototype
→ ends withnull
Constructor Functions and Prototypes
A constructor function in JavaScript is just a normal function.
function Person(name) {
this.name = name;
}
const alice = new Person("Alice");
- But when you call it with new, special things happen.
JavaScript does these steps under the hood:
Create a new empty object
Set the prototype of that object to the function’s prototype property.
obj.__proto__ = Person.prototype;
Call the constructor function with this bound to the new object:
Person.call(obj, "Alice");
Return the object unless the function explicitly returns something else.
return obj;
What is a class in javascript?
Think of it as a template for objects.
It's a blueprint for creating objects.
Encapsulates:
data (properties)
behavior (methods)
ES6 introduced the class
keyword for cleaner syntax.
Before ES6, JavaScript used functions and prototypes for object creation.
Code example for classes in javascript ?
class Person {
constructor(name, age) {
this.name = name; // property
this.age = age; // property
}
greet() { // method
console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
}
}
// Creating an instance
const person1 = new Person('Alice', 25);
person1.greet(); // Hello, my name is Alice and I am 25 years old.
class Person { ... } → defines the class.
constructor → special method called when you create a new object.
this → refers to the instance being created.
greet() → a method attached to the class.
What about class inheritance in javascript?
A class can inherit
from another class by using extends
.
class Person {
constructor(name, age) {
this.name = name; // property
this.age = age; // property
}
greet() { // method
console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
}
}
class Employee extends Person { // `extends` sets up inheritance.
constructor(name, age, position) {
super(name, age); // call parent constructor
this.position = position;
}
work() {
console.log(`${this.name} is working as a ${this.position}`);
}
}
const emp1 = new Employee('Bob', 30, 'Developer');
emp1.greet(); // Hello, my name is Bob and I am 30 years old.
emp1.work(); // Bob is working as a Developer
extends
→ sets up inheritance.super(...)
→ calls the parent class constructor.Child class can add new methods or override parent methods.
What is wrong with the subclass declaration below?
class Vehicle { // superclass
constructor() {
this.type = "Vehicle";
}
}
class Car extends Vehicle { // subclass
constructor() {
this.type = "Car";
}
}
const car = new Car();
console.log(car.type);
The subclass should call the constructor of the superclass(parent)
using the super()
keyword.
What are Object Methods?
A method is just a function stored as a property of an object.
Any property whose value is a function is essentially a method.
Access it using dot notation obj.key
or bracket notation obj["key"]
.
How do you iterate over properties of an object?
Use the
for let key in
syntax
for (let key in myObject){
const propVal = myObject[key]
}
Object.keys
method - keys only
const keysArray = Object.keys(objectOne)
...
Object.values
method - values only
const valsArray = Object.keys(objectOne)
...
Object.entries
method - keys and values
const obj = { a: 1, b: 2, c: 3 };
Object.entries(obj).forEach(([key, value]) => {
console.log(key, value);
});
How can we make sure that nobody can edit the properties of an object?
Using Object.freeze()
.
It freezes an object, so it cannot be modified in any way.
How can we make sure that nobody can add or delete properties to an object?
Using Object.seal()
.
It protects the properties of an object from being added or deleted but existing ones can be modified.
How can we make sure that nobody can edit the properties of an object?
const person = {
name: 'John',
};
Using Object.freeze()
such as Object.freeze(person)
.
Object.freeze()
freezes an object, so it cannot be modified in any way.
Common Mistakes:
Object.seal()
protects the properties of an object from being added or deleted, but existing ones can be modified.An object proxy does not explicitly prevent modification of the object, but it can be used to intercept and control access to it's properties.
const
means that the variable cannot be redeclared or redefined, but it does not prevent the modification of the object it points to.
"this", Regular Functions, Arrow Functions
How different type of functions handle the "this" object?
The behavior of this
in JavaScript is very tricky bc it depends on how a function is called, not just where it’s defined.
Normal functions:
this
depends on the call site.obj.method()
→this = obj
.new fn()
→this = new instance
.standalone → global/undefined.
can be changed with .call/.apply/.bind.
Arrow functions:
Never bind their own
this
.this
is always "lexically inherited" from surrounding scope.Great for callbacks where you don’t want
this
to change.
Ouput for code snippet with apply() ?
const numbers = [1, 1, 1];
function sumThis(a, b, c) {
return this + a + b + c;
}
console.log(sumThis.apply(5, numbers));
The global "this", what is the output to the console of the following snippet?
console.log(this);
Prints the value of this
to the console, in the global scope
In a
browser
:this
refers to thewindow
object.In
Node.js
:this
refers to{} (an empty object)
because the global scope in modules is not the same as the global object.
Output:
Browser -> Window {...}
Node.js -> {}
Normal functions and the "this" object?
Function declaration / function expression:
function foo() {
console.log(this);
}
foo();
Output:
"this" = global object (window in browsers, global in Node) in non-strict mode
"this" = undefined in strict mode
Normal functions with new
(constructor), the "this" object?
new
(constructor), the "this" object?A constructor function in JavaScript is just a normal function.
function Person(name) {
this.name = name;
}
const p = new Person("Alice");
console.log(p.name);
Output: "Alice".
Normal Function with Object method: what is the output to the console?
const obj = {
name: "John",
normalFunc: function() {
console.log(this.name);
}
};
obj.normalFunc();
For an object method with normal function, this
refers to the object (obj
).
Output: "John".
Object method with Arrow Function and "this": what is the output to the console?
const person = {
name: 'John',
sayHello: () => {
console.log(this);
},
};
person.sayHello();
Arrow functions don’t have their own this
, they inherit it from the surrounding lexical scope: the global this
(not person!)
Output:
Browser -> Window {...}
Node.js -> {}
Object methods with Normal Function and Arrow Function: what is the output to the console?
const obj = {
name: "ChatGPT",
normalFunc: function() {
console.log("Normal:", this); // obj
},
arrowFunc: () => {
console.log("Arrow:", this); // global object or undefined
}
};
obj.normalFunc();
obj.arrowFunc();
Output: Normal: obj
Arrow: undefined
(strict mode) or Arrow: window
(browser sloppy mode)
When to use what?
Use
normal functions
for object methods if you wantthis
to refer to theobject
.Use
arrow functions
when you want to preserve thethis
from the outer scope (e.g., inside a setTimeout or callback in a class method).
Class methods with Normal Function and Arrow Function: what is the output to the console?
class User {
constructor(name) {
this.name = name;
}
// Normal method
normalMethod() {
console.log("Normal:", this.name);
}
// Arrow function method
arrowMethod = () => {
console.log("Arrow:", this.name);
}
}
const user = new User("Alice");
// Direct call
user.normalMethod(); // "Normal: Alice"
user.arrowMethod(); // "Arrow: Alice"
// Extract methods
const normal = user.normalMethod;
const arrow = user.arrowMethod;
normal(); // Error: Cannot read property 'name' of undefined ("this" is lost)
arrow(); // "Arrow: Alice" (this is bound to the instance)
Normal method
Lives on the prototype.
Its
this
depends on how it’s called.If you detach it (const normal = user.normalMethod),
this
is no longer user — it becomes undefined (in strict mode).
Arrow method
Created as a property on the instance.
Lexically binds
this
at the moment the instance is created.Even if you detach it, it still points to the correct
this
(the instance).
When to use what?
Use normal methods most of the time (less memory overhead, fits standard OO style).
Use arrow functions in classes when you need to keep the method as a callback (e.g., event listeners, async code, React components).
Practical example with setTimeout: Normal Function and Arrow Function
class Timer {
constructor() {
this.seconds = 0;
}
// Normal function method
startNormal() {
setTimeout(function () {
this.seconds++;
console.log("Normal:", this.seconds);
}, 1000);
}
// Arrow function method
startArrow() {
setTimeout(() => {
this.seconds++;
console.log("Arrow:", this.seconds);
}, 1000);
}
}
const t = new Timer();
t.startNormal();
t.startArrow();
Output: t.startNormal(); // TypeError (this is undefined inside the callback) t.startArrow(); // "Arrow: 1"
Normal function: its this is not the class instance. It's the global scope. Arrow function: inherits this from its enclosing scope (startArrow method), it's bound to the class instance (t). So it correctly updates t.seconds
.
This is a classic case where arrow functions
save you from this headaches.
Class methods with Normal Function using self and bind: what is the output ?
TODO
Asynchronous & Events: Callbacks & Promises
TODO
Microtask Queue – Where
promises
are queued - faster thanmacrotasks
. only promises ???Macrotask Queue – Queue for
setTimeout
,setInterval
, etc. only callbacks???Event Bubbling – Events propagate from
child
→parent
. only callbacks???Event Capturing – Events propagate from
parent
→child
. only callbacks???Event Delegation – Handling events at a
higher level
for efficiency. only callbacks???
try/catch/finally throw
.then() and .catch() chaining instead of nesting
Promises
Promise – Object representing
async completion/failure
.
js : promise, syntax for handling resolve / reject
Async / Await – Syntactic sugar for
promises
.Microtask Queue – Where
promises
are queued - faster thanmacrotasks
.
Can you explain callbacks in human language?
A callback is just a function passed into another function, so it can call it later when it’s ready.
A piece of code that calls another piece of code once it has finished executing.
What's Callback Hell?
TLDR: Nested callbacks
, making code messy.
Example???
Can you use try/catch/finally on callbacks?
No.
Why? Because by the time the callback runs, the try/catch block has already finished executing.
try {
setTimeout(() => {
throw new Error("Oops");
}, 100);
} catch (err) {
// This won't catch the error
console.error("Caught:", err);
}
How Do You Use try/catch/finally block
?
try/catch/finally block
?try{}
→ risky operation handling - may throw error
- code blocks executes fully or partially - partially if error thrown
catch(){}
→ error handling - i.e. log error - code block may execute, if and only if an error is thrown
finally{}
→ cleanup handling - any additional tasks - code block always executes
Differences And Connection Between Promise vs Callback?
Callback is connected to Promise because callback is legacy js, it is the old way of handling asynchronous code.
Promise: an object that represents a value that may be available now, later, or never.
Provides chaining -
.then().then().catch()
, making sequences of async calls cleaner.Cleaner error handling, centralized with
.catch()
.Works seamlessly with
async
/await
, makingasync
code look synchronous.
Improvement List:
Syntax
Function passed into another
Object with .then()
, .catch()
Readability
Can lead to "callback hell"
More structured & chainable
Error handling
Must be handled manually
Built-in via .catch()
Async flow
Nested functions
Chaining or async/await
Multiple tasks
Hard to manage
Promise helpers (all
, race
, etc.)
Modern usage
Legacy / still used in Node APIs
Standard in modern JS
What Is A Promise?
An object that represents the eventual result of an asynchronous operation.
It acts like a placeholder for a value that will be available now, later, or never.
What Are Promise States?
3 states:
pending
→ initial state (still waiting).resolved
akafulfilled
→ operation completed successfully →resolve(value)
.rejected
→ operation failed →reject(error)
.
Once resolved
or rejected
it won’t change again.
What are the two key moments of a promise lifecycle?
Creation
Handling
Creation: What's The Code To Create A Promise With new()
Syntax ?
new()
Syntax ?const myPromise = new Promise((resolve, reject) => {
const success = true; // Do some async work, set to boolean depending on condition
if (success) {
resolve("Operation was successful!"); // resolve(value) → sends a success value
} else {
reject("Something went wrong."); // reject(error) → sends an error value
}
});
TLDR:
new Promise((resolve, reject) => { ... }
→ creates Promise object withresolve
andreject
as callbacksresolve(value) → sends a success value
reject(error) → sends an error value
Handling: What Are The Different Ways To Handle A Promise?
4 ways to handle a Promise:
Method Chaining →
.then()/.catch()/.finally()
(classic way?? )async/await
syntax +try/catch/finally
block (modern way?? )Attaching
.catch()
on theasync function call
Top-level await
(ES2022+) →await without async
, without wrapping in a function
5th way...to handle multiple Promises at once:
Promise utilities →
Promise.all
,Promise.race
, etc.
TODO: break down each in separate question ???
Can you call all of .then(), .catch(), and .finally() handlers ?
Yes, informally, you can.
But the handlers are the callback functions inside these methods.
promise
.then(onFulfilled, onRejected)
.catch(onRejected)
.finally(onFinally);
onFulfilled → handles the fulfilled result
onRejected → handles a rejection
onFinally → handles cleanup (runs regardless of outcome)
Promise Handling
With Method Chaining
Syntax: Code Example ?
Promise Handling
With Method Chaining
Syntax: Code Example ?TLDR:
.then()
→ for success.catch()
→ for errors.finally()
→ for cleanup
myPromise
.then(result => {
console.log("Resolved:", result); // runs if resolve() is called
})
.catch(error => {
console.error("Rejected:", error); // runs if reject() is called
})
.finally(() => {
console.log("Always runs, success or failure");
});
Promise Handling
With Method Chaining
: Code Example Handling 2 Promises ?
Promise Handling
With Method Chaining
: Code Example Handling 2 Promises ?TLDR:
.then()
→ for success.catch()
→ for errors.finally()
→ for cleanup
fetch("/api/data")
.then(res => res.json()) // 1st promise
.then(data => console.log(data)) // 2nd promise
.catch(err => console.error("Error handled here:", err)) // one error handler
.finally(() => console.log("Always runs")); // one finally handler
Promise Handling
With async/await
: Code Example ?
Promise Handling
With async/await
: Code Example ?Good Practice:
Always wrap your await in a try/catch/finally block
async function handlePromise() {
try {
const result = await myPromise; // waits until resolved
console.log("Resolved:", result);
} catch (error) {
console.error("Rejected:", error);
} finally {
console.log("Always runs");
}
}
handlePromise();
Promise Handling
With async/await
: Code Example Handling 2 Promises ?
Promise Handling
With async/await
: Code Example Handling 2 Promises ?Good Practice:
Always wrap your await in a try/catch/finally block
async function getData() {
try {
const res = await fetch("/api/data"); // typical fetch api call
const data = await res.json();
console.log(data);
} catch (err) {
console.error("Caught error:", err);
} finally {
console.log("Always runs");
}
}
getData();
Is This OK To Use async/await
Syntax Without try/catch/finally block
?
async/await
Syntax Without try/catch/finally block
?TLDR:
No. You can, but not ok for error handling.
You’ll get an unhandled rejection.
This may crash your program in modern runtimes.
async function run() {
const result = await Promise.reject("Oops!"); // Rejects
console.log(result); // This will never run !
}
run(); // UnhandledPromiseRejectionWarning (in Node.js) !
Good Practice:
Always wrap your await in a try/catch/finally block
When were try/catch/finally block
and promise chaining
and async/await
introduced?
try/catch/finally block
and promise chaining
and async/await
introduced?1999 (ES3) → try/catch/finally block
→ try{...} catch(e){...} finally{...}
for synchronous code only.
2015 (ES6) → Promise method chaining (v1) → .then().catch()
2017 (ES8) → async/await
intro'd → try/catch/finally block
and async/await
combined allow for asynchronous code (in await expressions
).
ES2018 → Promise method chaining (v2) → .finally()
What's the difference between Promise Chaining
and try/catch/finally block
?
Promise Chaining
and try/catch/finally block
?Promise Chaining → method-based style - uses methods
.then()
,.catch()
,.finally()
try/catch/finally Block → language syntax - uses keywords, not methods
Pro and Cons ???
how do Promise Chaining
and try/catch/finally block
and async/await
relate to one another?
What will the following function return?
async function getData(url) {
const response = await fetch(url);
const data = await response.json();
return data;
}
The function will return a Promise object. When the promise object resolves, the data will be returned.
Important: async functions always return a Promise that resolves to the value of the return statement.
Note: you'll have to add a try/catch block for error handling outside the function.
What Happens When Returning Anything Inside A Promise's .then() or .catch() ?
Warning: WEIRD one. But easy one.
It is passed to the next handler in the chain.
Returning Simple Value In then()
It’s automatically wrapped in a resolved Promise
for the next handler:
Promise.resolve(1)
.then(x => x + 1) // returns 2 → next .then receives 2
.then(y => console.log(y)); // logs 2
Returning a Promise In then()
The chain waits for it to settle, and passes along its result:
Promise.resolve(1)
.then(x => Promise.resolve(x + 1)) // returns Promise(2)
.then(y => console.log(y)); // logs 2
Returning Simple Value In catch()
Promise.reject(22)
.catch((x) => x + 1) // returns 23 → next .then receives 23
.then((y) => console.log(y)); // logs 23
Returning Simple Values In then() and catch()
Combining the previous cases.
Promise.reject(44)
.catch((x) => x + 1) // returns 45 → next .then receives 45
.then((y) => y + 1) // returns 46 → next .then receives 46
.then((z) => console.log(z)); // logs 46
Returning Nothing In then()
Promise.resolve(11)
.then((x) => {
console.log(x);
// no return
}) // returns undefined → next .then receives undefined
.then((y) => console.log(y)); // logs undefined
Returning A Value In catch()
, What Will Be The Output ?
catch()
, What Will Be The Output ?function job(state) {
return new Promise(function (resolve, reject) {
if (state) resolve("success");
else reject("error");
});
}
job(false)
.then(function (data) { // 1st then()
console.log(data);
})
.catch(function (error) {
console.log(error);
return "yow";
})
.then(function (data) { // 2nd then()
console.log(data);
});
Returns "yow" → this resolves the chain AGAIN (it does NOT rethrow). So it goes into the next then() and passes its argument
Output: error yow
What's Promise.all ?
TODO
Why Promise.all ?
TODO
Real-Life Use-Cases Promise.all ?
TODO
Promise.all
: What will be the console output?
Promise.all
: What will be the console output? const firstPromise = Promise.resolve(1);
const secondPromise = Promise.resolve(2);
const thirdPromise = Promise.reject("A bug occurred");
Promise.all([firstPromise, secondPromise, thirdPromise])
.then((values) => {
console.log(values);
})
.catch((error) => {
console.log(error);
});
A bug occurred.
Promise.all
is a method that takes an Array of promises and returns a new promise that resolves when all of the promises in the array have resolved.
However, if any of the promises is rejected, the promise returned will also reject, return the reason why it was rejected.
Macrotask vs Microtask and how the event loop
interacts with them?
event loop
interacts with them?TLDR: call stack -> microtask queue -> microtask queue.
Macrotask: Timer Functions: setTimeout
, setInterval
, setImmediate
, I/O: I/O
, UI rendering: UI rendering
Microtask: Promise
, MutationObserver
, process.nextTick
Order of execution:
call stack
once the call stack is empty, the event loop will execute the microtask queue.
once the microtask queue is empty, the event loop will move on to the next "tick" by picking the first task from the macrotask queue
In which order will the following be printed to the console?
frequent interview question! ⚠️
function cookLunch() {
const myFutureValue = new Promise((resolve, reject) => {
resolve(true);
});
myFutureValue.then(() => {
logger("Water Boiled ...");
});
setTimeout(() => {
logger("Dishes Washed ...");
}, 0);
logger("Lunch Cooked ...");
}
cookLunch();
To answer correctly, you need to understand the difference between macrotask
and microtask
and how the event loop interacts with them.
Macrotask: Timer Functions:
setTimeout
,setInterval
,setImmediate
, I/O:I/O
, UI rendering:UI rendering
Microtask:
Promise
,MutationObserver
,process.nextTick
In this case, the code will be executed in the following order:
logger("Lunch Cooked ...")
is called first and is synchronous, so it will be printed to the console first.setTimeout(() => { logger("Dishes Washed ...") }, 0)
is called second and will push the callback to themacrotask queue
.myFutureValue.then(() => { logger("Water Boiled ...") })
is called third and will push the callback to themicrotask queue
.Once the call stack is empty, the event loop will execute the microtask queue, so
logger("Water Boiled ...")
will be printed to the console second.Finally, once the microtask queue is empty, the event loop will move on to the next "tick" by picking the first task from the macrotask queue, so logger("Dishes Washed ...") will be printed to the console third.
Answer:
Lunch Cooked ..., Water Boiled ..., Dishes Washed ...
Last updated