JavaScript is one of the most important programming languages used on the web. It makes websites interactive, dynamic, and responsive.
But have you ever wondered what actually happens when you write JavaScript code and run it in a browser?
In this tutorial, we will understand how JavaScript works, how the browser executes JavaScript code, what the JavaScript engine does, and how concepts like the call stack, Web APIs, event loop, and callback queue work together.
What Happens When JavaScript Runs?
When you write JavaScript code such as:
let message = "Hello World";
console.log(message);
the browser does not simply read the code line by line and display the result.
Several components work together to execute the code.
The basic process looks like this:
JavaScript Code → JavaScript Engine → Call Stack → Execution → Output
For asynchronous operations, additional browser components such as Web APIs and the Event Loop become involved.
What is a JavaScript Engine?
A JavaScript engine is a program that reads and executes JavaScript code.
Every modern browser has a JavaScript engine.
Some popular engines are:
| Browser | JavaScript Engine |
|---|---|
| Google Chrome | V8 |
| Microsoft Edge | V8 |
| Firefox | SpiderMonkey |
| Safari | JavaScriptCore |
For example, Google Chrome uses the V8 JavaScript engine.
Node.js also uses the V8 engine to execute JavaScript outside the browser.
How Does the JavaScript Engine Execute Code?
A simplified JavaScript execution process looks like this:
- JavaScript code is received.
- The engine parses the code.
- The code is converted into an internal representation.
- The engine executes the code.
- The engine optimizes frequently executed code for better performance.
Modern JavaScript engines use techniques such as Just-In-Time (JIT) compilation to improve execution speed.
JavaScript is Single-Threaded
One of the most important concepts to understand is that JavaScript is traditionally described as single-threaded.
This means JavaScript has one main thread for executing JavaScript code.
For example:
console.log("First");
console.log("Second");
console.log("Third");
The statements execute in order:
First
Second
Third
JavaScript does not execute these three statements simultaneously on the main JavaScript thread.
However, this does not mean JavaScript cannot handle multiple tasks efficiently.
This is where the Web APIs, callback queue, and event loop become important.
What is the Call Stack?
The Call Stack keeps track of the functions that are currently being executed.
Consider this example:
function greet() {
console.log("Hello");
}
greet();
When JavaScript executes this code, the greet() function is added to the call stack.
The function runs and then is removed from the stack.
You can imagine the process like this:
Call Stack
greet()
console.log()
After execution finishes, the stack becomes empty.
How Functions Work in the Call Stack
Consider:
function first() {
second();
}
function second() {
console.log("Hello");
}
first();
The call stack works approximately like this:
first()
↓
second()
↓
console.log()
JavaScript completes the innermost operation first and then returns to the previous function.
This is why understanding the call stack is important when debugging JavaScript applications.
What Happens When a Function Takes Too Long?
Because JavaScript has a single main execution thread, a long-running operation can block other JavaScript work.
For example:
function block() {
let start = Date.now();
while (Date.now() - start < 5000) {
// Blocking the main thread
}
}
block();
console.log("Hello");
The console.log() statement cannot execute until the block() function finishes.
This is called blocking the main thread.
In real applications, blocking operations can make a website feel slow or unresponsive.
What are Web APIs?
Browsers provide many features that are not directly part of the JavaScript language itself.
These are commonly referred to as Web APIs.
Examples include:
setTimeout()- DOM APIs
- Fetch API
- Geolocation API
- Web Storage API
- Browser events
For example:
setTimeout(() => {
console.log("Hello");
}, 2000);
The timer is handled by the browser environment rather than waiting inside the JavaScript call stack for two seconds.
What is the Callback Queue?
When an asynchronous operation finishes, its callback needs to wait until JavaScript is ready to execute it.
The Callback Queue, also called the Task Queue, stores callbacks waiting to be processed.
For example:
setTimeout(() => {
console.log("Hello");
}, 2000);
console.log("JavaScript");
The output is:
JavaScript
Hello
The callback from setTimeout() does not immediately execute when the timer finishes.
It must wait until the call stack is available.
What is the Event Loop?
The Event Loop is one of the most important parts of JavaScript’s asynchronous behavior.
Its job is to check whether:
- The Call Stack is empty
- There are waiting tasks in the appropriate queue
When the call stack is empty, the Event Loop helps move a ready callback into the call stack so JavaScript can execute it.
A simplified model is:
JavaScript Code
↓
Call Stack
↓
Web APIs
↓
Callback Queue
↓
Event Loop
↓
Call Stack
This mechanism allows JavaScript to handle asynchronous operations without blocking the main thread while those operations are waiting.
Understanding setTimeout()
Let’s look at a common example:
console.log("Start");
setTimeout(() => {
console.log("Timeout");
}, 0);
console.log("End");
Many beginners expect:
Start
Timeout
End
But the actual output is:
Start
End
Timeout
Why?
Even though the timer is set to 0 milliseconds, its callback does not execute immediately.
The callback must wait until the current JavaScript execution finishes and the event loop can schedule it.
This is an important JavaScript concept.
How Fetch Works
Modern web applications frequently communicate with servers using the fetch() API.
Example:
fetch("/api/users")
.then(response => response.json())
.then(data => {
console.log(data);
});
The browser handles the network operation.
JavaScript can continue executing other code while the request is being processed.
When the request completes, the resulting promise reaction is scheduled for execution.
This is one reason JavaScript can build responsive web applications despite having a single main JavaScript execution thread.
What are Promises?
A Promise represents the eventual result of an asynchronous operation.
A promise can be in one of three states:
- Pending
- Fulfilled
- Rejected
Example:
const promise = fetch("/api/users");
promise
.then(response => response.json())
.then(data => {
console.log(data);
})
.catch(error => {
console.log(error);
});
Promises make asynchronous JavaScript easier to manage compared with deeply nested callbacks.
What is async/await?
async and await provide a cleaner way to write asynchronous JavaScript.
Example:
async function getUsers() {
const response = await fetch("/api/users");
const data = await response.json();
console.log(data);
}
getUsers();
Although the code looks synchronous, await does not block the entire JavaScript thread while the network request is waiting.
Instead, the asynchronous operation can continue, and the function resumes when the awaited promise settles.
JavaScript and the DOM
JavaScript can also interact with the Document Object Model (DOM).
The DOM represents the structure of an HTML document.
For example:
<h1 id="title">Hello</h1>
JavaScript can change the text:
document.getElementById("title").textContent = "Welcome to JavaScript";
The browser updates the webpage after JavaScript changes the DOM.
This is how JavaScript creates dynamic web pages.
JavaScript and Browser Events
JavaScript can listen for events generated by the user or browser.
For example:
const button = document.querySelector("#button");
button.addEventListener("click", () => {
alert("Button clicked!");
});
When the user clicks the button:
- The browser detects the click.
- The event is created.
- The registered event handler becomes ready to run.
- The event loop schedules it when the JavaScript execution environment is ready.
- The callback runs.
This is how JavaScript responds to user interaction.
JavaScript Execution Context
Another important concept is the Execution Context.
An execution context is the environment in which JavaScript code is evaluated and executed.
Commonly discussed execution contexts include:
- Global Execution Context
- Function Execution Context
- Eval Execution Context
For example:
let name = "Harshit";
function greet() {
let message = "Hello";
console.log(message);
}
greet();
JavaScript creates a global execution environment and creates a new function execution context when greet() runs.
Global Execution Context
When JavaScript starts executing a script, it creates the Global Execution Context.
Variables and functions defined at the global level belong to this global execution environment.
For example:
let name = "Harshit";
function greet() {
console.log("Hello");
}
The global environment contains the information required to execute this code.
Function Execution Context
Whenever a function is called, JavaScript creates a new execution context for that function.
Example:
function add(a, b) {
return a + b;
}
add(10, 20);
When add() runs, JavaScript creates a function execution context containing information such as its parameters and local variables.
Memory Heap
JavaScript also uses an area commonly called the Memory Heap to store objects and other dynamically allocated data.
For example:
const user = {
name: "Harshit",
age: 25
};
The object requires memory, and the JavaScript runtime manages that memory.
JavaScript also has garbage collection, which helps automatically reclaim memory that is no longer reachable by the program.
A Simple Overview of JavaScript Runtime
You can visualize the JavaScript runtime like this:
JavaScript Runtime
JavaScript
|
v
Call Stack
|
+--------------+--------------+
| |
v v
Web APIs JavaScript Engine
|
v
Task / Promise Queues
|
v
Event Loop
|
+-------> Call Stack
This is a simplified model. Modern browsers have more complex internals, and different queues have different scheduling rules.
Why JavaScript is Fast
Modern JavaScript engines are highly optimized.
They use techniques such as:
- JIT compilation
- Code optimization
- Inline caching
- Garbage collection
- Efficient memory management
JavaScript engines monitor how code runs and can optimize frequently executed code.
This allows modern JavaScript applications to perform complex tasks efficiently.
JavaScript in the Browser vs Node.js
JavaScript is not limited to browsers.
With Node.js, JavaScript can run outside the browser.
For example:
console.log("Hello from Node.js");
Node.js provides APIs for:
- File system operations
- HTTP servers
- Networking
- Database communication
- Process management
The JavaScript language remains JavaScript, but the runtime environment provides different APIs.
JavaScript Execution: The Big Picture
Let’s summarize the process.
When JavaScript code runs:
- The JavaScript engine receives the code.
- The code is parsed.
- The engine prepares it for execution.
- JavaScript code executes using the call stack.
- Synchronous operations run on the JavaScript thread.
- Browser or runtime APIs handle certain asynchronous operations.
- Completed asynchronous work is scheduled through the appropriate queues.
- The Event Loop coordinates when callbacks can run.
- JavaScript continues executing tasks as they become available.
Understanding this process makes concepts like promises, async/await, events, and callbacks much easier to understand.
Conclusion
JavaScript may look simple when you write a few lines of code, but a lot happens behind the scenes.
The JavaScript engine, call stack, Web APIs, queues, and event loop work together to execute code and handle asynchronous operations.
The most important concepts to remember are:
- JavaScript executes code using a JavaScript engine.
- The call stack manages currently executing functions.
- JavaScript has a single main execution thread.
- Browser APIs handle many asynchronous operations.
- Completed asynchronous work is scheduled through queues.
- The Event Loop coordinates when queued work can execute.
- Promises and
async/awaitprovide modern ways to work with asynchronous operations. - JavaScript can interact with the DOM to create dynamic websites.
Once you understand these concepts, debugging JavaScript and learning advanced topics such as promises, closures, asynchronous programming, and performance optimization becomes much easier.