JavaScript Debugging - Exercise 3
Buggy Code:
let original = {
name: "Alice",
greet: function() { return "Hi, " + this.name; }
};
let clone = JSON.parse(JSON.stringify(original));
console.log(clone.greet()); // TypeError
JSON.stringify cannot serialize functions. The greet method is silently dropped during serialization, so clone.greet is undefined. Fix: use Object.assign for a shallow clone that preserves methods, or write a custom clone function.
Fixed Code:
let original = {
name: "Alice",
greet: function() { return "Hi, " + this.name; }
};
let clone = Object.assign({}, original);
clone.name = original.name;
console.log(clone.greet()); // "Hi, Alice"
Buggy Code:
function fetchData() {
return new Promise(function(resolve, reject) {
reject("Network error");
});
}
fetchData()
.then(function(data) {
console.log(data);
})
.then(function() {
console.log("Done");
});
The rejected Promise is never handled with a .catch(). The error is silently swallowed because no error handler is attached. Fix: add a .catch() at the end of the chain.
Fixed Code:
function fetchData() {
return new Promise(function(resolve, reject) {
reject("Network error");
});
}
fetchData()
.then(function(data) {
console.log(data);
})
.then(function() {
console.log("Done");
})
.catch(function(err) {
console.log("Error: " + err);
});
Buggy Code:
function setupButton() {
let count = 0;
document.getElementById("btn").addEventListener("click", function() {
count++;
console.log("Clicked " + count + " times");
});
}
setupButton();
setupButton();
Calling setupButton() twice adds two separate click listeners to the same button. Each click fires both listeners, each with its own count variable. Fix: either call it once, or remove the previous listener before adding a new one.
Fixed Code:
function setupButton() {
let count = 0;
let btn = document.getElementById("btn");
function handleClick() {
count++;
console.log("Clicked " + count + " times");
}
btn.removeEventListener("click", handleClick);
btn.addEventListener("click", handleClick);
}
setupButton();
Buggy Code:
async function getNumber() {
return 42;
}
let result = getNumber();
console.log(result); // Promise { 42 }
An async function always returns a Promise, even if the return value is a plain number. You must await the result or use .then() to access the resolved value.
Fixed Code:
async function getNumber() {
return 42;
}
getNumber().then(function(result) {
console.log(result); // 42
});
Buggy Code:
let a = 0.1;
let b = 0.2;
if (a + b === 0.3) {
console.log("Equal");
} else {
console.log("Not equal: " + (a + b));
}
// Output: "Not equal: 0.30000000000000004"
JavaScript uses IEEE 754 floating point representation, which cannot precisely represent all decimal fractions. 0.1 + 0.2 produces 0.30000000000000004 due to rounding errors. Fix: compare using a small tolerance value (epsilon).
Fixed Code:
let a = 0.1;
let b = 0.2;
if (Math.abs((a + b) - 0.3) < Number.EPSILON) {
console.log("Equal");
} else {
console.log("Not equal: " + (a + b));
}
Buggy Code:
let arr = [1, 2, 3, 4, 5];
delete arr[2];
console.log(arr); // [1, 2, empty, 4, 5]
console.log(arr.length); // 5
The delete operator removes the property but does not re-index the array or change its length. It leaves an empty slot (a "hole") in the array. Fix: use splice to properly remove the element and re-index.
Fixed Code:
let arr = [1, 2, 3, 4, 5];
arr.splice(2, 1);
console.log(arr); // [1, 2, 4, 5]
console.log(arr.length); // 4
Buggy Code:
async function processItems(items) {
items.forEach(async function(item) {
await processItem(item);
console.log("Processed: " + item);
});
console.log("All done");
}
forEach does not wait for async callbacks. The await inside the callback creates individual Promises, but forEach ignores the return values. "All done" is logged before any item is processed. Fix: use a for...of loop, which properly awaits each iteration.
Fixed Code:
async function processItems(items) {
for (let item of items) {
await processItem(item);
console.log("Processed: " + item);
}
console.log("All done");
}
Buggy Code:
let user = {
firstName: "John",
lastName: "Doe",
get fullName() {
return this.fullName;
}
};
console.log(user.fullName);
The getter fullName references this.fullName, which calls itself recursively, causing a stack overflow. The getter should combine firstName and lastName instead of referencing its own property name.
Fixed Code:
let user = {
firstName: "John",
lastName: "Doe",
get fullName() {
return this.firstName + " " + this.lastName;
}
};
console.log(user.fullName); // "John Doe"
Buggy Code:
function flatten(arr) {
let result = [];
for (let i = 0; i < arr.length; i++) {
if (Array.isArray(arr[i])) {
result.concat(flatten(arr[i]));
} else {
result.push(arr[i]);
}
}
return result;
}
console.log(flatten([1, [2, [3, 4]], 5])); // [1, 5]
The concat method returns a new array and does not modify result in place. The return value of concat is never assigned back to result, so nested elements are lost. Fix: assign the result of concat back to result, or use push with the spread operator.
Fixed Code:
function flatten(arr) {
let result = [];
for (let i = 0; i < arr.length; i++) {
if (Array.isArray(arr[i])) {
result = result.concat(flatten(arr[i]));
} else {
result.push(arr[i]);
}
}
return result;
}
console.log(flatten([1, [2, [3, 4]], 5])); // [1, 2, 3, 4, 5]
Buggy Code:
function debounce(fn, delay) {
let timer;
return function() {
clearTimeout(timer);
timer = setTimeout(fn, delay);
};
}
let btn = document.getElementById("search");
btn.addEventListener("click", debounce(function(e) {
console.log(e.target);
}, 300));
The debounced function does not forward the arguments or the this context to the original function. When fn is called inside setTimeout, it receives no arguments, so e is undefined. Fix: capture the context and arguments, then pass them when calling fn.
Fixed Code:
function debounce(fn, delay) {
let timer;
return function() {
let context = this;
let args = arguments;
clearTimeout(timer);
timer = setTimeout(function() {
fn.apply(context, args);
}, delay);
};
}
let btn = document.getElementById("search");
btn.addEventListener("click", debounce(function(e) {
console.log(e.target);
}, 300));