Lesson 1.1: How JavaScript Executes Code
Duration: 50 minutes
Learning Objectives
By the end of this lesson, you will be able to:
- Understand what a JavaScript engine is and how it works
- Explain why JavaScript is single-threaded
- Describe the JavaScript runtime environment
- Understand the difference between the engine and the runtime
The JavaScript Engine
When you write JavaScript code, the computer cannot understand it directly. It needs a program to translate and execute your code. This program is called a JavaScript engine.
// Your code
const message = 'Hello, World!';
console.log(message);
// The engine reads this, understands it, and executes it
Popular JavaScript Engines
Different browsers and environments use different engines:
| Engine | Used In | Creator |
|---|---|---|
| V8 | Chrome, Node.js, Deno | |
| SpiderMonkey | Firefox | Mozilla |
| JavaScriptCore | Safari | Apple |
| Chakra | Edge (legacy) | Microsoft |
When you run TypeScript code, it first compiles to JavaScript, then the engine executes that JavaScript.
// TypeScript
const greet = (name: string): string => {
return `Hello, ${name}!`;
};
// Compiles to JavaScript, then V8 (or another engine) executes it
Single-Threaded Execution
JavaScript is single-threaded, meaning it can only do one thing at a time. Think of it like a single cashier at a store - they can only serve one customer at a time.
console.log('First'); // Executes first
console.log('Second'); // Waits, then executes
console.log('Third'); // Waits, then executes
// Output:
// First
// Second
// Third
The code executes from top to bottom, one line at a time. The engine finishes one operation before starting the next.
Why Single-Threaded?
JavaScript was created for the browser to manipulate web pages. Having multiple threads changing the same page elements could cause chaos:
// Imagine two threads running at the same time:
// Thread 1: Delete the button
// Thread 2: Change the button's color
// Which happens first? This would be unpredictable!
Single-threaded execution makes code predictable - you always know what runs when.
The Problem with Single-Threaded
If JavaScript can only do one thing at a time, what happens with slow operations?
// Imagine this takes 5 seconds
const data = fetchDataFromServer(); // Blocks everything!
// This has to wait 5 seconds to run
console.log('Ready!');
If fetching data takes 5 seconds, the entire program freezes. The user cannot click buttons, scroll, or do anything. This is called blocking.
We will solve this problem later in this module - that is where the event loop comes in!
The Runtime Environment
The JavaScript engine alone cannot do much. It needs a runtime environment that provides additional capabilities.
What the Engine Provides
The engine handles core JavaScript:
- Variables and data types
- Functions and scope
- Objects and arrays
- Loops and conditions
What the Runtime Provides
The runtime adds extra features:
In the Browser:
- DOM manipulation (
document.querySelector) - HTTP requests (
fetch) - Timers (
setTimeout,setInterval) - Storage (
localStorage) - User events (clicks, keyboard input)
In Node.js:
- File system access (
fs) - Network operations (
http) - Timers (
setTimeout,setInterval) - Process management (
process) - Operating system info (
os)
// This is JavaScript (engine handles it)
const numbers = [1, 2, 3];
const doubled = numbers.map((n) => n * 2);
// This is the runtime (browser provides it)
document.getElementById('app');
setTimeout(() => console.log('Timer!'), 1000);
fetch('https://api.example.com/data');
The Complete Picture
┌─────────────────────────────────────────────────────────┐
│ Runtime Environment │
│ ┌─────────────────┐ ┌─────────────────────────────┐ │
│ │ JavaScript │ │ Web APIs / │ │
│ │ Engine │ │ Node APIs │ │
│ │ │ │ │ │
│ │ - Variables │ │ - setTimeout/setInterval │ │
│ │ - Functions │ │ - fetch / http │ │
│ │ - Objects │ │ - DOM (browser) │ │
│ │ - Arrays │ │ - File system (Node) │ │
│ │ - Loops │ │ - Events │ │
│ └─────────────────┘ └─────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
How Code Execution Works
Let us trace through a simple program:
const name = 'Alice';
function greet(person: string): void {
const greeting = `Hello, ${person}!`;
console.log(greeting);
}
greet(name);
console.log('Done');
Step-by-Step Execution
- Line 1: Engine creates variable
namewith value"Alice" - Lines 3-6: Engine stores the function
greetin memory (does not run it yet) - Line 8: Engine sees
greet(name):- Calls the function with
"Alice" - Creates
greetingvariable inside the function - Executes
console.log(greeting)- prints "Hello, Alice!" - Function ends, returns to line 8
- Calls the function with
- Line 9: Engine executes
console.log("Done")- prints "Done"
Output:
Hello, Alice!
Done
Every line completes before the next one starts. This is synchronous execution.
Execution Context
When JavaScript runs code, it creates an execution context - a wrapper that keeps track of what is happening.
Global Execution Context
When your program starts, JavaScript creates a global context:
// Global context is created
const appName = 'MyApp';
function initialize(): void {
console.log('Starting ' + appName);
}
initialize();
The global context contains:
- Global variables (
appName) - Function declarations (
initialize) - The
thiskeyword (pointing to global object)
Function Execution Context
When a function is called, a new context is created:
function calculateArea(width: number, height: number): number {
// New execution context created here
const area = width * height;
return area;
// Context destroyed when function ends
}
const result = calculateArea(5, 10);
Each function call gets its own context with:
- Parameters (
width,height) - Local variables (
area) - Its own
thisvalue
Memory Management
The engine manages memory in two areas:
The Heap
Stores objects and complex data:
// These are stored in the heap
const user = { name: 'Alice', age: 25 };
const numbers = [1, 2, 3, 4, 5];
The Stack
Stores simple values and tracks function calls:
// These might be stored on the stack
const count = 42;
const isActive = true;
We will explore the stack in detail in the next lesson when we cover the call stack.
Practical Example
Let us see how execution order works in practice:
function multiply(a: number, b: number): number {
console.log(`Multiplying ${a} * ${b}`);
return a * b;
}
function square(n: number): number {
console.log(`Squaring ${n}`);
return multiply(n, n);
}
function printSquare(n: number): void {
console.log(`Starting with ${n}`);
const result = square(n);
console.log(`Result: ${result}`);
}
printSquare(5);
Execution Trace
1. printSquare(5) is called
-> "Starting with 5"
2. square(5) is called
-> "Squaring 5"
3. multiply(5, 5) is called
-> "Multiplying 5 * 5"
-> returns 25
4. square returns 25
5. printSquare continues
-> "Result: 25"
Output:
Starting with 5
Squaring 5
Multiplying 5 * 5
Result: 25
Exercises
Exercise 1: Predict the Output
What will this code print?
console.log('A');
function first(): void {
console.log('B');
}
function second(): void {
console.log('C');
first();
console.log('D');
}
console.log('E');
second();
console.log('F');
Solution
A
E
C
B
D
F
Explanation:
console.log("A")- prints "A"- Functions
firstandsecondare defined (not called yet) console.log("E")- prints "E"second()is called:- prints "C"
- calls
first()which prints "B" - prints "D"
console.log("F")- prints "F"
Exercise 2: Engine vs Runtime
Categorize each feature as "Engine" or "Runtime":
const x = 5setTimeout(fn, 1000)array.map(fn)fetch(url)for (let i = 0; i < 10; i++)document.querySelector(".btn")function add(a, b) { return a + b; }
Solution
const x = 5- Engine (variable declaration)setTimeout(fn, 1000)- Runtime (Web API / Node API)array.map(fn)- Engine (built-in array method)fetch(url)- Runtime (Web API)for (let i = 0; i < 10; i++)- Engine (loop construct)document.querySelector(".btn")- Runtime (DOM API, browser only)function add(a, b) { return a + b; }- Engine (function declaration)
Exercise 3: Trace the Execution
Write down the order of console.log outputs:
function a(): void {
console.log('a start');
b();
console.log('a end');
}
function b(): void {
console.log('b start');
c();
console.log('b end');
}
function c(): void {
console.log('c');
}
console.log('program start');
a();
console.log('program end');
Solution
program start
a start
b start
c
b end
a end
program end
The functions call each other in a chain, and each must complete before returning to the caller.
Key Takeaways
- JavaScript engine translates and executes your code (V8, SpiderMonkey, etc.)
- Single-threaded means one operation at a time - predictable but can block
- Runtime environment provides additional APIs (timers, HTTP, DOM, file system)
- Execution contexts track what is happening during code execution
- Synchronous execution means each line completes before the next starts
Resources
| Resource | Type | Description |
|---|---|---|
| MDN: JavaScript Engine | Documentation | What is a JS engine |
| Inside the JavaScript Runtime | Article | Deep dive into V8 |
| JavaScript.info: JavaScript Fundamentals | Tutorial | Execution basics |
Next Lesson
Now that you understand how JavaScript executes code, let us explore the call stack - the data structure that tracks function execution - and introduce the event loop.