JavaScript Fundamentals and the Browser
var Hoisting in Loops
Open the page in a browser, fill in some form field values, and submit. Check the console output. Are all the field values captured and reported correctly?
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>var Hoisting in Loops</title>
</head>
<body>
<h1>Collected field values</h1>
<ul id="output"></ul>
<script>
var fields = ["name", "email", "phone"];
var handlers = [];
for (var i = 0; i < fields.length; i++) {
handlers.push(function () {
return fields[i];
});
}
var list = document.getElementById("output");
handlers.forEach(function (handler, idx) {
var li = document.createElement("li");
li.textContent = fields[idx] + ": " + handler();
list.appendChild(li);
});
</script>
</body>
</html>
Show explanation
The bug is using var inside a loop. Hoisting causes the variable to be shared
across iterations, so the function returns undefined for some fields. Teaches
var hoisting, block scope, and why let and const are preferable.
Dead DOM Event Listener
Open the page and click the button several times. Does it keep working after the first click?
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Dead DOM Event Listener</title>
</head>
<body>
<div id="container">
<button id="btn">Click me</button>
</div>
<p id="output">Clicks: 0</p>
<p><em>Button refreshes in 3 seconds. Click it before and after.</em></p>
<script>
var count = 0;
var btn = document.getElementById("btn");
btn.addEventListener("click", function () {
count += 1;
document.getElementById("output").textContent = "Clicks: " + count;
});
setTimeout(function () {
document.getElementById("container").innerHTML =
'<button id="btn">Click me (refreshed)</button>';
}, 3000);
</script>
</body>
</html>
Show explanation
The bug is attaching the event listener to a DOM element that is replaced by the update, so the listener is discarded and the button stops working after the first click. Teaches event delegation and the difference between live and dead DOM references.
this Binding in Callbacks
Open the page and click the button several times. Does the counter increment by the expected amount each time?
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>this Binding in Callbacks</title>
</head>
<body>
<button id="btn">Increment</button>
<p id="output">Count: 0</p>
<script>
var counter = {
value: 0,
increment: function () {
this.value += 1;
document.getElementById("output").textContent = "Count: " + this.value;
}
};
document.getElementById("btn").addEventListener("click", counter.increment);
</script>
</body>
</html>
Show explanation
The bug is that this inside the callback refers to the button element rather than
the object that owns the counter, so the counter increments by the wrong amount.
Teaches this binding in JavaScript callbacks and how to use arrow functions or
.bind() to preserve context.
Promise Not Awaited
Open the page and check the data displayed against what the API returns. Is the data correct on the first load, or does it appear stale or blank?
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Promise Not Awaited</title>
</head>
<body>
<h1>Latest prices</h1>
<ul id="items"></ul>
<p id="status"></p>
<script>
function fetchPrices() {
return new Promise(function (resolve) {
setTimeout(function () {
resolve([
{ name: "Widget", price: 9.99 },
{ name: "Gadget", price: 24.99 },
{ name: "Doohickey", price: 4.99 }
]);
}, 500);
});
}
var prices = fetchPrices();
prices.forEach(function (item) { // TypeError: prices.forEach is not a function
var li = document.createElement("li");
li.textContent = item.name + ": $" + item.price;
document.getElementById("items").appendChild(li);
});
document.getElementById("status").textContent = "Done.";
</script>
</body>
</html>
Show explanation
The bug is that the fetch result is used before the Promise resolves (code runs
synchronously after an async call), so the page displays stale data. Teaches the
JavaScript event loop, Promises, and async/await.
localStorage in Private Mode
Open the page in a private browsing window. Does anything go wrong? Check the browser console for errors.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>localStorage in Private Mode</title>
</head>
<body>
<p id="output"></p>
<script>
var visits = parseInt(localStorage.getItem("visits") || "0") + 1;
localStorage.setItem("visits", String(visits));
document.getElementById("output").textContent = "Visit count: " + visits;
</script>
</body>
</html>
Show explanation
The bug is that localStorage throws a SecurityError in private browsing mode but
the code has no try/catch, so the script fails silently. Teaches browser storage
limitations and how to use the browser console and DevTools to observe thrown
exceptions.
Missing Viewport meta Tag
Open the page in a browser and use DevTools to simulate a narrow mobile screen. Does the layout look correct?
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Missing Viewport Meta Tag</title>
<style>
body { font-family: sans-serif; max-width: 600px; margin: 0 auto; padding: 1em; }
.card { border: 1px solid #ccc; border-radius: 4px; padding: 1em; margin: 1em 0; }
h2 { margin: 0 0 0.5em; }
.price { font-size: 1.5em; color: #080; }
</style>
</head>
<body>
<h1>Product Listing</h1>
<div class="card">
<h2>Widget</h2>
<p class="price">$9.99</p>
<p>Small, round, and useful.</p>
</div>
<div class="card">
<h2>Gadget</h2>
<p class="price">$24.99</p>
<p>Larger and more complicated.</p>
</div>
</body>
</html>
Show explanation
The bug is a missing <meta name="viewport"> tag that prevents mobile scaling, so
the page layout breaks on narrow screens even though the CSS looks correct. Teaches
how to use browser DevTools' device emulation and inspect computed styles.
CORS Header Missing
Open the HTML page in a browser and check the network tab in DevTools. Does the fetch request succeed, or do you see an error? Compare this with running the same request from the command line.
"""Simple API server that is missing the CORS header.
Run with: python cors_server.py
Then open cors.html in a browser and click "Fetch".
The browser will refuse the response with a CORS error;
the same request works fine from curl or Python.
"""
import json
from http.server import BaseHTTPRequestHandler, HTTPServer
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
if self.path == "/api/data":
body = json.dumps({"items": ["alpha", "beta", "gamma"]}).encode()
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.end_headers()
self.wfile.write(body)
else:
self.send_response(404)
self.end_headers()
def log_message(self, fmt, *args):
print(f"{self.address_string()} - {fmt % args}")
if __name__ == "__main__":
server = HTTPServer(("localhost", 8000), Handler)
print("Serving on http://localhost:8000 (Ctrl-C to stop)")
server.serve_forever()
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>CORS Header Missing</title>
</head>
<body>
<h1>CORS demo</h1>
<p>Start <code>cors_server.py</code>, then click Fetch.</p>
<button id="fetch-btn">Fetch</button>
<pre id="output">—</pre>
<script>
document.getElementById("fetch-btn").addEventListener("click", function () {
fetch("http://localhost:8000/api/data")
.then(function (r) { return r.json(); })
.then(function (data) {
document.getElementById("output").textContent = JSON.stringify(data, null, 2);
})
.catch(function (err) {
document.getElementById("output").textContent = "Error: " + err.message;
});
});
</script>
</body>
</html>
Show explanation
The bug is a missing Access-Control-Allow-Origin header on the server response, so
the fetch request returns a CORS error in the browser even though it works from the
command line. Teaches what CORS is, how to read network error messages in DevTools,
and how to configure server headers.
Browser Caching Stale JavaScript
Make a change to the JavaScript file and reload the page normally. Does the change take effect? Try checking the network tab in DevTools to see which version of the file the browser is serving.
var APP_VERSION = "1.0.0";
function formatPrice(cents) {
return "$" + (cents / 100).toFixed(2);
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Browser Caching</title>
<script src="caching.js"></script>
</head>
<body>
<h1>Price display</h1>
<p id="version"></p>
<p id="price"></p>
<script>
document.getElementById("version").textContent = "App version: " + APP_VERSION;
document.getElementById("price").textContent = "Item price: " + formatPrice(999);
</script>
</body>
</html>
Show explanation
The bug is that the browser is serving a cached version of the JavaScript file, so the page shows outdated content after a bug fix is deployed. Teaches cache-control headers, hard refresh vs. normal refresh, and how to use DevTools to disable the cache during development.
Missing Source Map
Trigger the JavaScript error and look at the stack trace in the browser console. Can you identify the exact source location of the problem from the information shown?
// Original readable source — not loaded by the page.
// This would be compiled/minified into sourcemap.min.js for deployment.
// Without a source map, errors point into the minified file, not here.
function processItems(items) {
return items.map(function (item) {
return item.toUpperCase();
});
}
document.getElementById("run-btn").addEventListener("click", function () {
var items = [1, 2, 3];
var result = processItems(items);
document.getElementById("output").textContent = result.join(", ");
});
function processItems(a){return a.map(function(i){return i.toUpperCase();})}document.getElementById("run-btn").addEventListener("click",function(){var items=[1,2,3];var result=processItems(items);document.getElementById("output").textContent=result.join(", ");});
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Missing Source Map</title>
</head>
<body>
<h1>Source map demo</h1>
<button id="run-btn">Run</button>
<p id="output"></p>
<script src="sourcemap.min.js"></script>
</body>
</html>
Show explanation
The bug is deploying a minified bundle without a source map, so JavaScript errors are reported on minified line numbers that do not correspond to the source. Teaches what source maps are, how to generate them, and how to load them in DevTools to see original source locations.
Content Security Policy Block
Open the page in a browser. Does the inline script run? Check the console for any policy-related messages.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Content Security Policy Block</title>
<meta http-equiv="Content-Security-Policy" content="default-src 'self'">
</head>
<body>
<h1>Dashboard</h1>
<p id="greeting"></p>
<p id="timestamp"></p>
<script>
document.getElementById("greeting").textContent = "Welcome back!";
document.getElementById("timestamp").textContent =
"Loaded at " + new Date().toLocaleTimeString();
</script>
</body>
</html>
Show explanation
The bug is a Content Security Policy header that blocks an inline script, so the application shows blank content in production even though it works in development. Teaches how to read CSP violation reports in the browser console, how CSP directives work, and how to move inline scripts to external files to comply.