All About JavaScript
1. Synchronous vs Asynchronous
What is Synchronous?
JavaScript by default is synchronous and single-threaded.
- Single-threaded = can do only one task at a time. It has only one “hand” with which all the work gets done.
- Synchronous = code runs line-by-line, top to bottom, one by one. As long as one line is not complete, the next line does not start.
console.log("1. Make tea");
console.log("2. Drink tea");
console.log("3. Wash cup");
// Output:
// 1. Make tea
// 2. Drink tea
// 3. Wash cupHere everything simply ran in order.
Where does the problem arise? Suppose some task takes time like fetching data from a server, or a 5-second timer. In a synchronous world, until that task is complete, the entire program stops (blocking).
console.log("Start");
// Suppose this is a 5-second blocking task
let now = Date.now();
while (Date.now() - now < 5000) {
// Nothing for 5 seconds, just wait… browser got HANGED
}
console.log("End");In these 5 seconds the browser will freeze completely, button won’t click, won’t scroll, nothing. This blocking behavior is very bad.
What is Asynchronous?
Asynchronous means start some long task, let it run on the side, and keep running the rest of the code ahead. When that task gets completed, then its result will be handled.
Real life example: Restaurant waiter
- Synchronous waiter: takes order from one table, stands in the kitchen until the food is ready, then serves, then goes to the next table. (Slow, everyone waits.)
- Asynchronous waiter: takes order, gives it to the kitchen, and immediately goes to take orders from other tables. When the food is ready, then serves it.
Async waiter is more efficient. JavaScript also does the same for async work.
console.log("Start");
setTimeout(() => {
console.log("This ran after 3 seconds");
}, 3000);
console.log("End");
// Output:
// Start
// End
// (after 3 seconds) This ran after 3 secondsLook — setTimeout put the 3-second task on the side, and the code didn’t stop. “End” got printed immediately. This is non-blocking behavior. This is the power of async.
When is Async needed?
- Fetching data from Server/API (network requests)
- Reading/writing files
- Timers (setTimeout, setInterval)
- Database operations
- Waiting for some user action
Remember: JS itself is single-threaded, but the browser (or Node.js) helps JS in handling async work on the side. How? that we will see in detail ahead in the Event Loop section.
2. Callbacks
What is a Callback?
Callback is a function that is passed as an argument to another function, so later that function can be “called back”.
In simple words: “When this work gets done, then run this function.”
function sayNamaste(name) {
console.log("Namaste " + name);
}
function welcome(callback) {
let user = "Bikram";
callback(user); // here callback was called
}
welcome(sayNamaste);
// Output: Namaste BikramHere sayNamaste is a callback function which was passed to welcome.
Callbacks are of 2 types:
- Synchronous callbacks — runs immediately (like arr.map, arr.forEach).
- Asynchronous callbacks — runs after the completion of some task (like setTimeout).
// Synchronous callback example
[1, 2, 3].forEach(function (num) {
console.log(num * 2);
});
// 2, 4, 6 - immediatelysetTimeout — once, after some time
setTimeout(function, delay) runs a function once after the given delay (milliseconds).
setTimeout(() => {
console.log("2 seconds are done!");
}, 2000); // 2000 ms = 2 secondsImportant point: delay does not mean “exactly after this much time”. It means “at least after this much time”. Why? Because the callback has to wait in a queue.
setTimeout(() => console.log("Hello"), 0);
console.log("World");
// Output:
// World
// Hello
// (Yes! Even after 0 ms "World" comes first - the reason for this is event loop)If arguments are to be passed to callback:
function greet(name, city) {
console.log(Hi ${name} from ${city});
}
setTimeout(greet, 1000, "Dipi", "Kolkata");
// After 1 second: Hi Dipi from KolkatasetInterval — again and again, repeat
setInterval(function, delay) keeps running a function again and again (repeatedly) at every delay ms until you stop it.
let count = 1;
setInterval(() => {
console.log("Count: " + count);
count++;
}, 1000);
// Every 1 second:
// Count: 1
// Count: 2
// Count: 3
// ...infinite, until you stopclearTimeout and clearInterval — for stopping
Both setTimeout and setInterval return an ID. Using this ID you can cancel the timer.
clearTimeout(id) — cancels pending setTimeout (before running).
let timerId = setTimeout(() => {
console.log("This will never print");
}, 3000);
clearTimeout(timerId); // timer got cancelledclearInterval(id) stops running setInterval.
let count = 1;
let intervalId = setInterval(() => {
console.log("Count: " + count);
count++;
if (count > 5) {
clearInterval(intervalId); // stop after 5
console.log("Interval stopped");
}
}, 1000);
// Count: 1
// Count: 2
// Count: 3
// Count: 4
// Count: 5
// Interval stoppedTip: If using
setInterval, don’t forget to putclearInterval, otherwise it will keep running forever and can cause memory leak / performance issue.
Callback patterns
In async work there is a common pattern: “when work is done, run callback”. Database, file, network — everywhere.
function bringData(callback) {
console.log("Bringing data from server...");
setTimeout(() => {
let data = { id: 1, name: "Bikram" };
callback(data); // work complete, now call callback
}, 2000);
}
bringData((result) => {
console.log("Data received:", result);
});
// Bringing data from server…
// (after 2 sec) Data received: { id: 1, name: 'Bikram' }Error-first callbacks (Node.js style)
There is a standard pattern in industry: callback’s first argument is error, and second is data.
function bringData(callback) {
setTimeout(() => {
let error = null;
let data = { id: 1, name: "Bikram" };
if (error) {
callback(error, null);
} else {
callback(null, data); // first null = no error
}
}, 1000);
}
bringData((err, data) => {
if (err) {
console.log("Error came:", err);
return;
}
console.log("Success:", data);
});Pattern: callback(error, data). Always check error first, then use data.
Callback Hell (Pyramid of Doom) — THE PROBLEM
Problem comes when after one async task, second task, then third… dependent tasks like these have to be done. Inside every task’s callback the next callback has to be stuffed, and the code becomes a rightward leaning pyramid.
Real example: give order → do payment → confirm order → send email. Every step depends on previous one.
getUser(1, (user) => {
getOrders(user.id, (orders) => {
getOrderDetails(orders[0], (details) => {
processPayment(details, (payment) => {
sendConfirmation(payment, (result) => {
console.log("All Done:", result);
// and inside...inside...inside
});
});
});
});
});See this triangle/pyramid shape — this itself is called “Pyramid of Doom” or “Callback Hell”.
Its problems:
- Hard to read — code keeps running to the right.
- Error handling nightmare — separate error handling has to be done at every level.
- Hard to maintain — if something is to be added/changed in middle, mind spins.
- Painful to debug.
Promises came to solve this exact problem.
3. Promises
What is a Promise?
Promise is an object that represents the future result (or error) of an async operation.
Real life analogy: You ordered online. You immediately get a promise of “order confirmed”.
This promise currently:
- is pending (order on the way),
- later can be delivered (success), or
- can be cancelled (failure).
Promise is a commitment of “I will give result (or error) in future”.
3 States of Promise
A promise is always in one of these three states:
Important: Once a promise becomes fulfilled or rejected, it never changes again. This is called “settled”. Promise settles only once.
Making promise with new Promise
const myPromise = new Promise((resolve, reject) => {
// this is "executor function" - runs immediately
let workSuccessful = true;
if (workSuccessful) {
resolve("Work done! "); // fulfilled
} else {
reject("Something went wrong "); // rejected
}
});new Promise()takes a function which is called executor.- Executor receives 2 functions:
resolve(for success) andreject(for failure). - Call
resolve(value)and promise becomesfulfilledwith that value. - Call
reject(error)and promise becomes rejected.
Async example:
function bringData() {
return new Promise((resolve, reject) => {
console.log("Bringing data...");
setTimeout(() => {
let success = true;
if (success) {
resolve({ id: 1, name: "Bikram" });
} else {
reject("Server is down!");
}
}, 2000);
});
}Consuming Promise — .then, .catch, .finally
To get result of Promise we attach .then(), .catch() and .finally().
.then(callback)— runs on promise being fulfilled, gives result..catch(callback)— runs on promise being rejected, gives error..finally(callback)— always runs on promise settlement (whether success or fail). Useful for cleanup (like stopping loading spinner).
bringData()
.then((data) => {
console.log("Success:", data);
})
.catch((error) => {
console.log("Error:", error);
})
.finally(() => {
console.log("Work finished (whether pass or fail)");
});
// Bringing data…
// (after 2 sec)
// Success: { id: 1, name: 'Bikram' }
// Work finished (whether pass or fail)Now compare with callback hell — how neat and clean it is!
Promise Chaining
Here is real magic. .then() returns a new promise, that’s why you can attach .then() after .then() like a chain. Whatever you return from .then, comes as input in the next .then.
function add(num) {
return new Promise((resolve) => {
setTimeout(() => resolve(num + 10), 1000);
});
}
add(0)
.then((result) => {
console.log(result); // 10
return add(result); // returned new promise
})
.then((result) => {
console.log(result); // 20
return add(result);
})
.then((result) => {
console.log(result); // 30
});
// (1s) 10
// (2s) 20
// (3s) 30Now the callback hell example with Promise chaining — flat and readable:
getUser(1)
.then((user) => getOrders(user.id))
.then((orders) => getOrderDetails(orders[0]))
.then((details) => processPayment(details))
.then((payment) => sendConfirmation(payment))
.then((result) => console.log("All done:", result))
.catch((error) => console.log("If error comes anywhere then here:", error));Pyramid finished! Code goes straight down, not right.
Error propagation in chains
Best thing about Promise chain: a single .catch() catches all errors of entire chain. Error came anywhere in chain (or any promise rejected), it falls through straight down to .catch(), skipping .then in middle.
add(0)
.then((result) => {
console.log(result); // 10
throw new Error("Error in middle! ");
})
.then((result) => {
// THIS WILL BE SKIPPED, because error came above
console.log("This will not run:", result);
})
.then((result) => {
console.log("This too will not run");
})
.catch((error) => {
console.log("Error caught:", error.message); // Error in middle!
});
// 10
// Error caught: Error in middle! Remember: Attach a single .catch() in end it becomes error handler of entire chain. No need for separate error handling with every .then.
Promise Combinators — multiple promises together
Many times we have to handle more than one promises together. JS has given 4 helpful methods:
Promise.all all must be successful
Takes array of multiple promises. When All gets fulfilled, then gives an array of results. If even any single one gets rejected, entire Promise.all gets rejected immediately.
const p1 = Promise.resolve("Tea");
const p2 = Promise.resolve("Samosa");
const p3 = Promise.resolve("Biscuit");
Promise.all([p1, p2, p3])
.then((results) => {
console.log(results); // ['Tea', 'Samosa', 'Biscuit']
})
.catch((err) => {
console.log("Any one failed:", err);
});Use case: 3 separate API calls together, data of all needed only then proceed ahead. “All or nothing”.
// If even one fails:
const p1 = Promise.resolve("Tea");
const p2 = Promise.reject("Samosa finished!");
const p3 = Promise.resolve("Biscuit");
Promise.all([p1, p2, p3])
.then((results) => console.log(results))
.catch((err) => console.log("Fail:", err)); // Fail: Samosa finished!Promise.race — whichever settles first
Whichever promise settles (fulfill OR reject) first, gives its result/error. Winner of race.
const fast = new Promise((res) => setTimeout(() => res("Fast "), 1000));
const slow = new Promise((res) => setTimeout(() => res("Slow "), 3000));
Promise.race([fast, slow])
.then((winner) => console.log(winner)); // Fast (after 1 sec)Use case: Making timeout — “if API doesn’t come in 5 sec then fail it”.
Promise.allSettled need result of all, whether fail or pass
Waits for all promises to settle, and tells status of all whether fulfilled or rejected. This never rejects.
const p1 = Promise.resolve("Pass 1");
const p2 = Promise.reject("Fail 2");
const p3 = Promise.resolve("Pass 3");
Promise.allSettled([p1, p2, p3])
.then((results) => console.log(results));
/*
[
{ status: 'fulfilled', value: 'Pass 1' },
{ status: 'rejected', reason: 'Fail 2' },
{ status: 'fulfilled', value: 'Pass 3' }
]
*/Use case: When you need result of every task, even if some fail. (e.g., load profiles of 10 users, show whichever loads, skip whichever fails.)
Promise.any — need first SUCCESS
Whichever promise gets FULFILLED first, gives its result. Ignores rejections. If all get rejected, then gives AggregateError.
const p1 = Promise.reject("Fail 1");
const p2 = new Promise((res) => setTimeout(() => res("Success 2!"), 1000));
const p3 = Promise.reject("Fail 3");
Promise.any([p1, p2, p3])
.then((result) => console.log(result)); // Success 2! (rejections ignored)Difference between race vs any:
race= whichever settles first (whether fail or pass).any= whichever fulfills (success) first, ignores fails.
Quick comparison table
4. async / await
async/await is syntactic sugar on top of Promises. It makes async code look just like synchronous easiest to read and write. Inside Promises themselves are running.
Syntax and Rules
Rule 1: Making function async is mandatory to use await.
Rule 2: async function always returns a Promise.
Get Bikramjit Roy’s stories in your inbox
Join Medium for free to get updates from this writer.
Rule 3: await waits for Promise to settle and extracts and gives its resolved value.
function bringData() {
return new Promise((resolve) => {
setTimeout(() => resolve("Data came "), 2000);
});
}
// async function
async function main() {
console.log("Start");
const data = await bringData(); // will stop and wait here
console.log(data); // Data came
console.log("End");
}
main();
// Start
// (after 2 sec)
// Data came
// End.then() version vs async/await — both do same work:
// .then() version
bringData().then((data) => console.log(data));
// async/await version (more readable)
async function show() {
const data = await bringData();
console.log(data);
}await works only inside async function. (In modern JS top-level await is allowed in modules, but remember the inside-function rule.)
Error handling — try/catch
In place of .then()/.catch(), in async/await we use normal try/catch just like synchronous code.
function bringData() {
return new Promise((resolve, reject) => {
setTimeout(() => reject("Server down! "), 1000);
});
}
async function main() {
try {
const data = await bringData();
console.log("Success:", data);
} catch (error) {
console.log("Error caught:", error); // Error caught: Server down!
} finally {
console.log("Cleanup done");
}
}
main();
// (after 1 sec)
// Error caught: Server down!
// Cleanup doneLook — try/catch/finally works exactly like .then/.catch/.finally, but feels natural to read.
Sequential vs Parallel execution (Very Important!)
This concept is very important in interviews and real code both.
Sequential (one after another) — SLOW if tasks are independent
function work(name, time) {
return new Promise((resolve) => {
setTimeout(() => resolve(${name} done), time);
});
}
async function sequential() {
console.time("sequential");
const a = await work("A", 2000); // 2 sec wait
const b = await work("B", 2000); // then 2 sec more wait
const c = await work("C", 2000); // then 2 sec more wait
console.log(a, b, c);
console.timeEnd("sequential"); // ~6000 ms
}
sequential();Here every await waits for the one before it. Total 6 seconds — useless, because all three tasks do not depend on each other!
Parallel (all together) — FAST
If tasks are independent, start them together and wait with Promise.all.
async function parallel() {
console.time("parallel");
// Started all three immediately (didn't put await right now)
const pa = work("A", 2000);
const pb = work("B", 2000);
const pc = work("C", 2000);
// Now wait for result of all together
const [a, b, c] = await Promise.all([pa, pb, pc]);
console.log(a, b, c);
console.timeEnd("parallel"); // ~2000 ms
}
parallel();Total only 2 seconds! Because all three tasks ran side-by-side.
Golden Rule: If tasks depend on each other → sequential (await one after another). If independent → parallel (Promise.all). App becomes slow with wrong choice.
Common Mistakes
Mistake 1: Forgetting to put await
async function wrong() {
const data = bringData(); // await missing!
console.log(data); // Promise { } - not data, got Promise object!
}
async function correct() {
const data = await bringData(); // put await
console.log(data); // real data
}Without await you don’t get actual value, but pending Promise object. Very common bug.
Mistake 2: Forgetting to put try/catch
// Wrong - if rejected then crash with "unhandled promise rejection"
async function wrong() {
const data = await bringData(); // if failed app crash
console.log(data);
}
// Correct - handled error
async function correct() {
try {
const data = await bringData();
console.log(data);
} catch (e) {
console.log("Handled:", e);
}
}
Mistake 3: Await in loop in wrong way (forEach trap)
await inside forEach does not work as you think - it does not wait!
// Wrong - forEach does not wait for await
async function wrong(items) {
items.forEach(async (item) => {
await process(item); // all these will fire together, order won't remain
});
console.log("Done"); // this will print first of all! wrong
}
// Correct - use for…of if sequence needed
async function correct(items) {
for (const item of items) {
await process(item); // this will wait properly one by one
}
console.log("Done"); // properly in last
}Mistake 4: Awaiting independent work sequentially (slow)
This is the “sequential vs parallel” point above — parallelize independent work with Promise.all, not with one-by-one await.
5. The Event Loop (The Big “WOW” Moment)
This section is JS’s most important concept. If you understand this, you have gone quite ahead in JS. Read carefully!
Question: JS is single-threaded (only one work at a time), then how does it handle async tasks (timers, network) together without blocking?
Answer: Because of Event Loop. Come, let’s understand every piece.
Components: first understand these 4 things
Call Stack (revisited)
Where JS actually runs code. This is a stack (LIFO — Last In First Out). When function is called, it gets pushed onto stack. When it returns, gets popped.
function three() { console.log("three"); }
function two() { three(); }
function one() { two(); }
one();
// Call stack flow:
// one() push
// two() push
// three() push → "three" print → three() pop
// two() pop
// one() popImportant: On Call stack only one task runs at a time. If some long (blocking) work comes here, everything stops.
Web APIs / Browser APIs
setTimeout, fetch, DOM events — all these are not part of JS! Browser provides these (C++ APIs in Node.js). When you call setTimeout, JS gives it to browser and moves ahead. Browser handles that timer on the side. Because of this JS does not block.
Callback Queue (Macrotask Queue)
When Web API work completes (like timer over, or event fired), its callback comes here in Callback Queue (or Macrotask Queue / Task Queue) and stands in line. This is a queue (FIFO — First In First Out).
In Macrotasks come: setTimeout, setInterval, setImmediate, I/O, UI events.
Microtask Queue
A separate and higher priority queue. In this come:
- Promise callbacks (
.then,.catch,.finally) - Code after await
queueMicrotask()
Most important rule: Priority of Microtask queue is ALWAYS higher than macrotask (callback) queue.
What is work of Event Loop?
Event Loop continuously checks like a watchman:
“Is Call Stack empty? If yes, first empty Microtask Queue (completely), then pick one task from Callback (Macrotask) Queue and put on stack.”
Steps:
- Run synchronous code completely first (on Call Stack).
- On stack being empty, run all microtasks (finish complete microtask queue).
- Then pick one macrotask.
- Then back to step 2 (check microtasks), then next macrotask…
- This loop keeps running. That’s why name — Event Loop.
Step-by-step example
console.log("1");
setTimeout(() => console.log("2"), 0);
Promise.resolve().then(() => console.log("3"));
console.log("4");Output: 1, 4, 3, 2
Come let’s trace:
- console.log(“1”) → sync → 1 printed immediately.
- setTimeout(…) → given to browser. After timer (0ms) callback will go to Macrotask queue.
- Promise.resolve().then(…) → callback goes to Microtask queue.
- console.log(“4”) → sync → 4 printed immediately.
- Sync code finished. Call stack empty.
- Event loop: microtasks first → .then callback ran → 3 printed.
- Now macrotask → setTimeout callback → 2 printed.
Microtask (3) beat macrotask (2) — even if setTimeout was 0 ms! This is magic of event loop.
Tricky Output Prediction Problems
These type of questions definitely come in interview. Practice!
Problem 1
console.log("A");
setTimeout(() => console.log("B"), 0);
Promise.resolve()
.then(() => console.log("C"))
.then(() => console.log("D"));
console.log("E");Output: A, E, C, D, B
- Sync: A, E.
- Microtasks:
Cran..thenafter.thencreates a new microtask →D. (Microtask queue finishes completely at once, including new microtasks.) - Macrotask: B.
Problem 2
console.log("Start");
setTimeout(() => console.log("Timeout 1"), 0);
Promise.resolve().then(() => {
console.log("Promise 1");
setTimeout(() => console.log("Timeout 2"), 0);
});
setTimeout(() => console.log("Timeout 3"), 0);
console.log("End");Output: Start, End, Promise 1 , Timeout 1, Timeout 3 , Timeout 2
Sync: Start, End.
Microtask: Promise 1 (and inside it a new setTimeout got scheduled which creates macrotask for Timeout 2 — but in last of queue).
Macrotasks order: Timeout 1, Timeout 3 (these were already in queue), then Timeout 2 (added later).
Problem 3 (async/await one — a bit advanced)
async function async1() {
console.log("async1 start");
await async2();
console.log("async1 end"); // this after await - becomes microtask
}
async function async2() {
console.log("async2");
}
console.log("script start");
async1();
console.log("script end");Output:
script start
async1 start
async2
script end
async1 endconsole.log(“script start”)→ sync.async1()call → “async1 start” print → await async2() → “async2” print. Code after await (async1 end) becomes microtask and stops.console.log(“script end”)→ sync.- Sync finished → microtask ran →
async1 end.
Key insight: Complete code after await behaves like a microtask it runs after current sync code.
Remember rule: Sync code → then all Microtasks (Promises, after await) → then Macrotasks (setTimeout). Always in this order.
6. Fetch API and HTTP Requests 🌐
Till now we saw fake/timer async work. Now real network calls bringing actual data from server.
Basic idea of HTTP requests
When you go to a website, your browser sends an HTTP request to server, and server gives response. Common request types (methods):
- GET — bringing data (read)
- POST — sending new data (create)
- PUT/PATCH — updating data
- DELETE — deleting data
fetch() — built-in function
fetch(url) returns a Promise. That’s why with it we use .then or async/await.
.then version
fetch("https://jsonplaceholder.typicode.com/users/1")
.then((response) => response.json()) // convert response to JSON (this too is promise)
.then((data) => console.log(data))
.catch((error) => console.log("Error:", error));Important two-step process:
fetch() → gives response object (not raw data right now).
response.json() → parses response body into JS object (this too returns a promise, so this too needs await/.then).
async/await version (recommended — clean)
async function fetchUser() {
try {
const response = await fetch("https://jsonplaceholder.typicode.com/users/1");
// Check whether request was successful or not
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const data = await response.json();
console.log("User received:", data);
} catch (error) {
console.log("Something went wrong:", error.message);
}
}
fetchUser();Very Important : fetch rejects only when network fails (no internet). If server gives 404 or 500 error, still promise of fetch resolves (does not reject)! That’s why checking
response.okorresponse.statusyourself is mandatory. Beginners miss this a lot.
POST request — sending data
async function createUser() {
try {
const response = await fetch("https://jsonplaceholder.typicode.com/users", {
method: "POST", // request type
headers: {
"Content-Type": "application/json", // telling that sending JSON
},
body: JSON.stringify({ // convert JS object to JSON string
name: "Bikram",
email: "Bikram@abc.com",
}),
});
const data = await response.json();
console.log("Created:", data);
} catch (error) {
console.log("Error:", error.message);
}
}
createUser();Pay attention:
method: “POST”— what kind of request.headers— tell server in what format sending data.body— actual data, which has to be made string withJSON.stringify()(data travels in string on network).
Now let’s talk about this JSON in detail.
7. JSON, JSON.parse, JSON.stringify
What is JSON?
JSON = JavaScript Object Notation. This is a standard way of writing data in text (string) format. Data travels between servers and clients in this format only, because only text can be sent on network (not objects directly).
JSON looks like JS object, but there are some rules:
- Keys must always be in double quotes:
“name”(single quote or without quote won’t work). - Strings always in double quotes.
- Values can be: string, number, boolean, null, array, or another object.
Functions,undefinedare not allowed in JSON.
{
"name": "Bikram",
"age": 27,
"isStudent": true,
"skills": ["JS", "React"],
"address": {
"city": "Kolkata",
"pin": 700120
}
}JS object vs JSON: In JS object keys work without quote and in single quote. In JSON strictly double quotes needed. JSON is a string, JS object is an actual object.
JSON.stringify() — Object → String
Converts JS object/array to JSON string. (Mandatory while sending data to server — like saw above in POST request.)
const user = {
name: "Bikram",
age: 27,
skills: ["JS", "React"],
};
const jsonString = JSON.stringify(user);
console.log(jsonString);
// {"name":"Bikram","age":27,"skills":["JS","React"]}
console.log(typeof jsonString); // string For Pretty printing (readable format) indentation in 3rd argument:
console.log(JSON.stringify(user, null, 2));
/*
{
"name": "Bikram",
"age": 27,
"skills": [
"JS",
"React"
]
}
*/Note: JSON.stringify skips functions and undefined:
const obj = {
name: "Bikram",
greet: function () {}, // this will be skipped
age: undefined, // this too skipped
};
console.log(JSON.stringify(obj)); // {"name":"Bikram"}JSON.parse() — String → Object
Converts JSON string back into usable JS object. (Mandatory on receiving data from server like response.json() does this inside.)
const jsonString = '{"name":"Bikram","age":27,"skills":["JS","React"]}';
const user = JSON.parse(jsonString);
console.log(user); // { name: 'Bikram', age: 27, skills: [ 'JS', 'React' ] }
console.log(user.name); // Bikram
console.log(user.skills[0]); // JS
console.log(typeof user); // object ✅Error on parsing invalid JSON:
try {
JSON.parse("{name: 'Bikram'}"); // keys/strings not in double-quote
} catch (e) {
console.log("Invalid JSON:", e.message);
}
// Always keep parse in try/catch if source is not trustedstringify ⇄ parse are opposite of each other
const original = { a: 1, b: [2, 3] };
const str = JSON.stringify(original); // object → string
const back = JSON.parse(str); // string → object
console.log(back); // { a: 1, b: [ 2, 3 ] } - back to sameHere is the complete translation in simple, clean, and clear English, structured in the exact same format and depth.
Bonus: Deep Copy Hack!
JSON.parse(JSON.stringify(obj)) is an old trick to create a deep copy of an object (it copies nested objects as well, breaking the reference link).
Limitation: It does not handle functions, undefined, or Date objects properly (functions/undefined are stripped out, and dates are converted to strings). However, it works perfectly fine for simple data structures.
const original = { name: "Bikram", address: { city: "Kolkata" } };
const copy = JSON.parse(JSON.stringify(original));
copy.address.city = "Barrackpore";
console.log(original.address.city); // Kolkata (original remains safe! )
console.log(copy.address.city); // Barrackpore







