JavaScript debugging is the process of finding why your code is not working and fixing the real cause.
A button may not respond. A variable may contain the wrong value. A DOM selector may return null. An API request may fail. A loop may run too many times.
Instead of changing random lines until the problem disappears, debugging gives you a clear way to inspect what JavaScript is doing.
Course Home: JavaScript Tutorial for Beginners
Previous Lesson: JavaScript localStorage Explained
Next Lesson: JavaScript Projects for Beginners
Quick Answer
Start JavaScript debugging with four checks:
- Open the browser Console.
- Read the complete error message.
- Check the file name and line number.
- Inspect the value that caused the problem.
For example:
const price = 500;
console.log(price);
You can also pause code with:
debugger;
Or set a breakpoint in browser DevTools.
A good debugging process is:
Reproduce the problem
↓
Read the error
↓
Find the failing line
↓
Inspect values
↓
Test one cause
↓
Fix it
↓
Test again
What Is JavaScript Debugging?
Debugging means finding and correcting problems in your JavaScript code.
A bug can cause:
- Wrong output
- Missing content
- Broken buttons
- Failed form validation
- Incorrect calculations
- Empty API results
- Console errors
- Infinite loops
- Page features that stop working
Debugging is not only about fixing syntax.
Sometimes the code is valid JavaScript but the logic is wrong.
Why Debugging Is an Essential JavaScript Skill
Every developer writes code that fails sometimes.
The important skill is knowing how to find the cause.
For example, this looks simple:
const productPrice = 500;
console.log(productprice);
The browser may show:
ReferenceError: productprice is not defined
The problem is capitalization.
You created:
productPrice
but tried to use:
productprice
JavaScript is case-sensitive.
A useful error message can point you directly toward the problem.
Start With the Browser Console
The Console is one of the first tools you should open when JavaScript does not work.
In most desktop browsers, open Developer Tools and choose the Console panel.
The Console can show:
- JavaScript errors
- Warnings
- Your
console.log()output - Network-related errors
- Failed resource messages
- Values you inspect manually
If your script seems to do nothing, check the Console before changing your code.
Test Whether Your JavaScript File Loaded
Add this near the top of your JavaScript file:
console.log("JavaScript loaded");
Reload the page.
If you see:
JavaScript loaded
your script file is being executed.
If you do not see it, check:
- The
tag - File name
- Folder path
- Browser Console
- Network panel
- Whether the file was saved
If script loading is still unclear, review How to Add JavaScript to HTML.
console.log()
console.log() prints values to the browser Console.
Example:
const price = 500;
const quantity = 3;
console.log(price);
console.log(quantity);
Output:
500
3
You can also inspect a calculation:
const total = price * quantity;
console.log(total);
Output:
1500
Add Labels to Console Output
This:
console.log(total);
may become confusing when you have many logs.
Prefer:
console.log(
"Cart total:",
total
);
Output:
Cart total: 1500
Labels make debugging faster.
Log Several Values Together
Example:
console.log({
price,
quantity,
total
});
This prints an object containing all three values.
It is useful when several variables affect one result.
console.error()
Use:
console.error()
for error information.
Example:
console.error(
"Product request failed"
);
Browsers usually display it with error styling.
Do not use console.error() as a replacement for proper user-facing error messages.
It is mainly a developer tool.
console.warn()
Use:
console.warn()
for a warning.
Example:
if (stock < 5) {
console.warn(
"Stock is running low"
);
}
Warnings can help during development when something is unusual but not necessarily a fatal error.
console.table()
console.table() is useful for arrays and objects.
Example:
const products = [
{
name: "Keyboard",
price: 1500
},
{
name: "Mouse",
price: 700
}
];
console.table(products);
The browser can display the values in a table-like format.
This is easier to scan than several long object logs.
console.dir()
console.dir() can help inspect JavaScript objects and DOM elements.
Example:
const button =
document.querySelector("#button");
console.dir(button);
This can expose the element's properties and methods in an object-style view.
console.group()
You can group related logs.
Example:
console.group("Checkout");
console.log(
"Price:",
500
);
console.log(
"Quantity:",
2
);
console.log(
"Total:",
1000
);
console.groupEnd();
This can make a busy Console easier to read.
console.time()
You can measure how long code takes.
Example:
console.time("Calculation");
let total = 0;
for (
let i = 0;
i < 100000;
i++
) {
total += i;
}
console.timeEnd(
"Calculation"
);
The Console reports the elapsed time.
Use performance tools for deeper analysis, but console.time() is useful for quick checks.
Remove Temporary Logs Before Production
Debugging logs can be useful while developing.
But do not leave a large number of unnecessary logs in production code.
They can:
- Clutter the Console
- Expose internal details
- Make real errors harder to notice
- Reduce code clarity
Keep logs that have a clear operational purpose.
Remove temporary debugging output after fixing the issue.
Read the Entire Error Message
Suppose the Console shows:
ReferenceError: totalPrice is not defined
Do not focus only on the word:
Error
Read:
totalPrice is not defined
That tells you the important part.
The Console may also show:
- File name
- Line number
- Column number
- Stack trace
Use all of that information.
Click the Error Location
Browser DevTools often lets you click a file and line number in the Console.
For example:
script.js:27
Clicking it can open the Sources panel at the exact line.
This is much faster than manually searching a large file.
Common JavaScript Error Types
Beginners will often see:
SyntaxErrorReferenceErrorTypeErrorRangeError
Understanding the message is more important than memorizing every error class.
JavaScript SyntaxError
A SyntaxError means JavaScript cannot correctly parse the code.
Example:
const user = {
name: "Riya"
age: 25
};
The comma is missing.
Correct:
const user = {
name: "Riya",
age: 25
};
Another example:
if (age >= 18 {
console.log("Adult");
}
The closing parenthesis is missing.
Correct:
if (age >= 18) {
console.log("Adult");
}
Common SyntaxError Causes
Check for:
- Missing commas
- Missing parentheses
- Missing braces
- Unclosed strings
- Incorrect quotes
- Extra brackets
- Invalid keywords
- Misspelled syntax
Code editors can catch many of these problems before the browser runs the script.
JavaScript ReferenceError
A ReferenceError often means JavaScript cannot find the identifier you tried to use.
Example:
console.log(userName);
when userName was never declared.
Another example:
const productName =
"Keyboard";
console.log(productname);
The capitalization does not match.
Correct:
console.log(productName);
ReferenceError From Scope
Example:
if (true) {
const message = "Hello";
}
console.log(message);
message is not available outside the block.
This can produce a ReferenceError.
Review JavaScript Variables if scope is still unclear.
JavaScript TypeError
A TypeError often means you are trying to perform an operation on a value that does not support it.
Example:
const user = null;
console.log(user.name);
JavaScript cannot read name from null.
Another common DOM example:
const button =
document.querySelector(
"#wrongId"
);
button.addEventListener(
"click",
() => {
console.log("Clicked");
}
);
If no element matches, button is:
null
Calling:
button.addEventListener()
then causes a TypeError.
Fix a null DOM Selector
First check the selector:
console.log(button);
If it prints:
null
check:
- The HTML ID
- Selector spelling
#for IDs.for classes- Script timing
- Whether the element exists on that page
Correct example:
const button =
document.querySelector(
"#buyButton"
);
if (button) {
button.addEventListener(
"click",
() => {
console.log(
"Clicked"
);
}
);
}
Review JavaScript DOM Manipulation for selector details.
JavaScript RangeError
A RangeError can happen when a value is outside an allowed range.
One example is invalid recursion that exceeds the call stack.
Example:
function repeat() {
repeat();
}
repeat();
The function calls itself forever.
Eventually, the browser may report a stack-related range error.
Recursive functions are more advanced, but this example shows that valid syntax can still create runtime failures.
Error Messages Can Differ
Different browsers may phrase JavaScript errors differently.
Focus on:
- Error type
- Variable or property name
- File
- Line
- Stack trace
- What value was actually present
Do not depend on one exact browser error sentence.
What Is a Stack Trace?
A stack trace shows the sequence of function calls that led to an error.
Example:
function calculateTotal() {
return getPrice();
}
function getPrice() {
return missingPrice;
}
calculateTotal();
The error may show that:
getPrice()
failed and was called by:
calculateTotal()
That call history helps you find how the broken code was reached.
Read a Stack Trace From the Top
The first relevant line often points close to where the error happened.
Then follow the calling functions downward.
Ignore unrelated browser or library frames at first unless your code points there.
The goal is to find the first line in your own code that receives or creates the wrong value.
Logical Errors Do Not Always Throw Errors
Consider:
const price = 500;
const quantity = 3;
const total =
price + quantity;
console.log(total);
Output:
503
JavaScript does not throw an error.
The code is valid.
The logic is wrong.
You probably wanted:
const total =
price * quantity;
Output:
1500
This is a logic bug.
Debug Logic by Checking Intermediate Values
Instead of only checking the final output:
console.log(total);
inspect each input:
console.log(
"price:",
price
);
console.log(
"quantity:",
quantity
);
console.log(
"total:",
total
);
This helps you find where the value first becomes wrong.
Test One Assumption at a Time
Suppose a cart total is wrong.
Do not change five lines at once.
Check:
- Is
pricecorrect? - Is
quantitycorrect? - Are they numbers?
- Is the operator correct?
- Is the result updated at the right time?
This keeps debugging controlled.
Check Data Types
A common bug is mixing strings and numbers.
Example:
const price = "500";
const shipping = 100;
console.log(
price + shipping
);
Output:
500100
Check:
console.log(
typeof price
);
Output:
string
Then convert when appropriate:
const numericPrice =
Number(price);
Review JavaScript Data Types if type conversion causes confusion.
Check Boolean Conditions
Suppose:
const age = 20;
if (age > 21) {
console.log(
"Access allowed"
);
}
You expected the block to run.
Inspect the condition:
console.log(
age > 21
);
Output:
false
Now the problem is clear.
Debugging conditions often means logging the boolean expression itself.
Debug Multiple Conditions Separately
Instead of only logging:
console.log(
age >= 18 &&
hasTicket &&
!blocked
);
inspect each part:
console.log(
"is adult:",
age >= 18
);
console.log(
"has ticket:",
hasTicket
);
console.log(
"not blocked:",
!blocked
);
This reveals which condition fails.
Review JavaScript Logical Operators for combined conditions.
Use Browser DevTools Sources Panel
The Sources panel lets you inspect JavaScript files and pause execution.
Common debugging tools include:
- Breakpoints
- Step over
- Step into
- Step out
- Scope inspection
- Watch expressions
- Call stack
- Console while paused
These tools let you see your program one step at a time.
What Is a Breakpoint?
A breakpoint tells the browser:
Pause JavaScript when execution reaches this line.
While paused, you can inspect:
- Variables
- Function arguments
- Objects
- Current scope
- Call stack
- Expressions
This is often better than adding dozens of console.log() calls.
Set a Breakpoint in DevTools
Open your JavaScript file in the Sources panel.
Click the line number where you want JavaScript to pause.
Then reproduce the action that reaches that line.
For example:
function calculateTotal(
price,
quantity
) {
const total =
price * quantity;
return total;
}
Set a breakpoint on:
const total =
price * quantity;
When the function runs, inspect:
price
quantity
before JavaScript continues.
What Is the debugger Statement?
You can pause JavaScript directly from your code with:
debugger;
Example:
function calculateTotal(
price,
quantity
) {
debugger;
return price * quantity;
}
When DevTools is open and the line is reached, execution can pause.
Remove temporary debugger statements when you finish debugging.
Step Over
Step over runs the current line and pauses at the next line in the same general flow.
Use it when you do not need to enter a called function.
Example:
const total =
calculateTotal(
price,
quantity
);
Step over executes calculateTotal() without walking through every line inside it.
Step Into
Step into enters a function call so you can debug the function line by line.
Use it when you suspect the bug is inside the called function.
Step Out
Step out finishes the current function and pauses after returning to the caller.
Use it when you entered a function but no longer need to inspect every remaining line.
Resume Script Execution
The resume button continues execution until:
- Another breakpoint
- Another
debuggerstatement - An exception pause
- Program completion
Use breakpoints strategically instead of pausing every line.
Inspect Scope While Paused
When execution is paused, DevTools can show variables available in the current scope.
For example:
function calculate(
price,
quantity
) {
const tax = 100;
debugger;
return (
price * quantity +
tax
);
}
While paused, inspect:
price
quantity
tax
This is a direct way to verify the values JavaScript is actually using.
Watch Expressions
A Watch panel lets you track expressions while stepping through code.
Examples:
price * quantity
cart.length
user?.name
The value updates as the program changes.
This is useful for values you need to observe repeatedly.
Conditional Breakpoints
A conditional breakpoint pauses only when a condition becomes true.
For example, in a loop:
for (
let i = 0;
i < 100;
i++
) {
// code
}
You may only want to pause when:
i === 50
A conditional breakpoint avoids stepping through the first 50 iterations.
Debug Loops With Counters
Suppose:
for (
let i = 0;
i <= products.length;
i++
) {
console.log(
products[i]
);
}
The final output includes:
undefined
Inspect:
console.log({
i,
length:
products.length,
value:
products[i]
});
You will see that when:
i === products.length
the index is outside the array.
Correct:
i < products.length
Review JavaScript Loops for array-loop rules.
Debug Infinite Loops Carefully
Example:
let count = 1;
while (count <= 5) {
console.log(count);
}
count never changes.
The condition remains true.
Correct:
let count = 1;
while (count <= 5) {
console.log(count);
count++;
}
If a page becomes unresponsive during testing, an infinite loop may be one possible cause.
Check the loop's:
- Starting value
- Condition
- Update
Debug Functions by Checking Inputs and Outputs
Suppose:
function calculateDiscount(
price,
percent
) {
return (
price -
percent
);
}
The formula is wrong.
Log the inputs:
console.log({
price,
percent
});
Then test a known case:
console.log(
calculateDiscount(
1000,
10
)
);
Ask:
What should the output be?
Small known test cases make function bugs easier to isolate.
Debug return Values
A common issue is forgetting return.
Example:
function add(a, b) {
a + b;
}
const result =
add(2, 3);
console.log(result);
Output:
undefined
Inspect the function.
Correct:
function add(a, b) {
return a + b;
}
Review JavaScript Functions if return behavior is unclear.
Debug Arrays With console.table()
Example:
const cart = [
{
name: "Keyboard",
quantity: 2,
price: 1500
},
{
name: "Mouse",
quantity: 1,
price: 700
}
];
console.table(cart);
This makes it easy to check each row.
For one item:
console.log(
cart[0]
);
For array size:
console.log(
cart.length
);
Debug Objects Property by Property
Suppose:
const product = {
name: "Keyboard",
price: 1500
};
This returns:
undefined
console.log(
product.productPrice
);
Inspect the object:
console.log(product);
The property is:
price
not:
productPrice
Correct:
console.log(
product.price
);
Use Object.keys() While Debugging
Example:
console.log(
Object.keys(product)
);
Output:
["name", "price"]
This helps confirm which property names actually exist.
Review JavaScript Objects for object access.
Debug DOM Selectors
HTML:
<button id="buyButton">
Buy
</button>
Wrong selector:
const button =
document.querySelector(
"#buy-button"
);
Check:
console.log(button);
Output:
null
Compare the selector with the actual HTML.
Correct:
const button =
document.querySelector(
"#buyButton"
);
Inspect DOM Elements in DevTools
The Elements panel shows the live DOM.
Use it to confirm:
- Element exists
- ID is correct
- Class is correct
- Attribute is present
- CSS class was added
- Element is hidden
- Content was updated
If JavaScript says it added a class, inspect the element and verify the class is really there.
Debug classList Changes
Example:
menu.classList.add(
"open"
);
Check:
console.log(
menu.classList
);
Or inspect the element in the Elements panel.
If the class exists but the visual change does not happen, the bug may be in CSS rather than JavaScript.
JavaScript Bug or CSS Bug?
Suppose JavaScript correctly runs:
menu.classList.add(
"open"
);
The HTML becomes:
<nav class="menu open">
but the menu is still invisible.
Now inspect your CSS.
The JavaScript may be correct.
Good debugging identifies which layer is actually broken.
Debug Event Listeners
Suppose clicking a button does nothing.
Start with:
console.log(button);
Confirm the element exists.
Then add:
button.addEventListener(
"click",
() => {
console.log(
"Click detected"
);
}
);
If the log appears, the event listener works.
The bug is probably in the code after the event fires.
If it does not appear, inspect the selector and event registration.
Debug event.target
When using event delegation:
list.addEventListener(
"click",
(event) => {
console.log(
event.target
);
}
);
Click different parts of the child element.
This shows which element actually started the event.
Then use:
event.target.closest(
".remove-button"
);
when appropriate.
Review JavaScript Events for event delegation.
Debug Form Validation
Suppose the form is always blocked.
Log each validation result:
const nameIsValid =
validateName();
const emailIsValid =
validateEmail();
console.log({
nameIsValid,
emailIsValid
});
If one is false, test that function separately.
Also inspect:
input.value
input.validity
input.checked
depending on the control.
Review JavaScript Form Validation for validation APIs.
Debug Async Code
Asynchronous bugs can be confusing because results arrive later.
Example:
async function loadData() {
console.log(
"Before request"
);
const response =
await fetch(
"/api/data"
);
console.log(
"After response",
response
);
const data =
await response.json();
console.log(
"Parsed data",
data
);
}
Strategic logs show how far the function gets before failing.
Use try...catch for Async Errors
Example:
async function loadData() {
try {
const response =
await fetch(
"/api/data"
);
if (!response.ok) {
throw new Error(
`HTTP ${response.status}`
);
}
const data =
await response.json();
console.log(data);
} catch (error) {
console.error(
"Load failed:",
error
);
}
}
This makes the error path visible.
Review JavaScript Async/Await for async error handling.
Debug Fetch Requests With the Network Panel
The Network panel is essential when fetch() does not behave as expected.
Inspect the request and check:
- Request URL
- HTTP method
- Status code
- Request headers
- Request body
- Response headers
- Response body
- Timing
- CORS errors
This is often more useful than guessing from the JavaScript code alone.
Check the Request URL
A wrong endpoint can cause:
404
Inspect the Network panel.
Compare the requested URL with the API documentation.
A simple typo can make the whole request fail.
Check the HTTP Method
If the API expects:
POST
but your request uses:
GET
the server may reject it or return an unexpected result.
Inspect the Network panel's method column.
Check the Request Body
For a JSON request:
body:
JSON.stringify(data)
inspect the request payload.
Confirm:
- Correct property names
- Correct values
- Correct data types
- No missing required fields
Do not assume the object you intended to send is the object that was actually sent.
Check Response Status
Inspect:
200
201
204
400
401
403
404
500
The status helps identify whether the problem is:
- Request data
- Authentication
- Permissions
- Missing resource
- Server failure
Then inspect the response body for more context.
Check response.ok in Code
Remember:
fetch()
does not normally reject only because of an HTTP status such as 404.
Use:
if (!response.ok) {
throw new Error(
`HTTP ${response.status}`
);
}
Review JavaScript Fetch API for this important behavior.
Debug JSON Parsing Errors
Suppose:
const data =
JSON.parse(text);
throws an error.
Log the raw value first:
console.log(text);
You may discover:
- Invalid JSON
- HTML error page
- Empty string
- Trailing comma
- Wrong response format
Do not repeatedly change JSON.parse() if the real problem is the input data.
Check Content-Type for API Responses
Example:
console.log(
response.headers.get(
"content-type"
)
);
If you expected JSON but received:
text/html
the server may have returned an HTML error page.
That explains why:
response.json()
fails.
Debug localStorage
Suppose:
const cart =
JSON.parse(
localStorage.getItem(
"cart"
)
);
fails.
Check the raw stored value:
const storedCart =
localStorage.getItem(
"cart"
);
console.log(storedCart);
Then determine whether it is:
null- Valid JSON
- Old data
- Corrupted data
- Wrong key
Review JavaScript localStorage for safe parsing.
Use the Application Panel
Browser DevTools often has an Application or storage-related panel.
Use it to inspect:
- localStorage
- sessionStorage
- Cookies
- IndexedDB
- Cache storage
For localStorage, confirm:
- Key exists
- Value is correct
- Old values were removed
- JSON looks valid
This is much faster than guessing what is stored.
Pause on Exceptions
DevTools can pause JavaScript automatically when an exception occurs.
Useful options often include:
- Pause on uncaught exceptions
- Pause on caught exceptions
This can stop JavaScript exactly where the error is thrown.
It is especially useful when the error is caught later and the original source is hard to find.
Event Listener Breakpoints
DevTools can also pause when browser events occur.
Examples may include:
- Mouse clicks
- Keyboard events
- Form submission
- Timers
This can help when you do not know which handler is running.
The exact DevTools layout varies by browser.
DOM Breakpoints
Some browser DevTools let you pause when a DOM element changes.
Possible triggers include:
- Child changes
- Attribute changes
- Node removal
This is useful when an element unexpectedly disappears or a class changes without an obvious cause.
Network Breakpoints and XHR/Fetch
Some DevTools can pause when a URL pattern is requested.
This helps trace which JavaScript code started a specific API request.
Use it when many parts of an application call the same endpoint.
Use the Pretty Print Tool
Minified JavaScript can look like one long line.
DevTools often includes a pretty-print feature that formats it into readable lines.
This can make debugging third-party or bundled code easier.
For your own source code, keep the development version readable before minification.
Source Maps
Production JavaScript may be bundled or minified.
Source maps can connect production code back to the original source files.
Modern development tools often generate them.
If DevTools shows your original source instead of one compressed bundle, source maps may be helping.
This becomes more important when you work with build tools, TypeScript, and frameworks.
Debugging Third-Party Code
If the error stack points into a library, first ask:
- Did my code pass invalid data?
- Am I using the library correctly?
- Did the API change?
- Is the library loaded correctly?
- Is there a version conflict?
Do not immediately edit third-party library code.
Find the first relevant call from your own code.
Reproduce the Bug Reliably
A bug is easier to fix when you can repeat it.
Write down the exact steps.
For example:
1. Open product page
2. Set quantity to 3
3. Click Add to Cart
4. Cart count becomes 1 instead of 3
Now you have one clear behavior to debug.
Reduce the Problem
If a feature contains 200 lines, isolate the smallest part that still fails.
For example, test only:
const quantity = 3;
const cartCount = 1;
console.log(
quantity,
cartCount
);
A smaller reproduction removes unrelated code and makes the real bug easier to see.
Use Known Input and Expected Output
For a calculation:
Input: 500 × 3
Expected: 1500
Actual: 503
This immediately suggests the wrong operator.
For a function:
Input: age 20
Expected: true
Actual: false
Now inspect the condition.
Debugging becomes easier when "correct" is clearly defined.
Change One Thing at a Time
Avoid making several speculative fixes together.
If the bug disappears, you will not know which change solved it.
A better process:
- Form one theory.
- Make one change.
- Test.
- Keep or undo it.
- Move to the next theory.
This prevents new bugs from hiding the original one.
Do Not Silence Errors Without Fixing Them
Avoid:
try {
brokenCode();
} catch (error) {
// do nothing
}
The error disappears from view, but the bug remains.
If a failure is intentionally handled, provide a meaningful recovery path or at least useful developer logging.
Do Not Use Optional Chaining to Hide Every Bug
This:
user?.profile?.name
is useful when data may genuinely be missing.
But do not use optional chaining only to stop an error when the property is required.
If user should always exist, a missing user may indicate a real bug that needs fixing.
Use defensive syntax when absence is valid, not as a universal error silencer.
Do Not Add Random setTimeout() Delays
A timing bug may tempt you to write:
setTimeout(() => {
runCode();
}, 1000);
without understanding why.
This can hide race conditions instead of fixing them.
Wait for the real completion signal:
- Promise
- Event
- API response
- DOM readiness
- Framework lifecycle
Use time delays only when time itself is part of the requirement.
Avoid Debugging by Random Code Changes
Changing:
const
to:
let
will not fix a missing selector.
Changing == to === will not fix a 404 API endpoint.
Match the fix to the evidence.
A Practical JavaScript Debugging Workflow
Use this workflow whenever a feature breaks.
1. Reproduce the Bug
Know exactly what action causes it.
2. Open DevTools
Check the Console first.
3. Read the Error
Use the error type, message, file, and line number.
4. Inspect the Failing Line
Ask what values that line expects.
5. Check the Actual Values
Use logs, breakpoints, or Watch expressions.
6. Trace Backward
Find where the wrong value came from.
7. Test One Fix
Change only what the evidence supports.
8. Retest the Original Case
Confirm the bug is gone.
9. Test Nearby Cases
Make sure the fix did not break another scenario.
10. Remove Temporary Debug Code
Clean up logs and debugger statements that are no longer needed.
Real Website Example: Broken Add to Cart
HTML:
<button
id="addToCart"
data-product-id="101"
>
Add to Cart
</button>
<span id="cartCount">0</span>
JavaScript with a bug:
const button =
document.querySelector(
"#add-to-cart"
);
const cartCount =
document.querySelector(
"#cartCount"
);
button.addEventListener(
"click",
() => {
cartCount.textContent =
"1";
}
);
The button does nothing.
Step 1: Check the Console
You may see a TypeError because:
button
is null.
Step 2: Log the Selector Result
console.log(button);
Output:
null
Step 3: Compare HTML and JavaScript
HTML ID:
addToCart
JavaScript selector:
#add-to-cart
They do not match.
Step 4: Fix the Selector
const button =
document.querySelector(
"#addToCart"
);
Now the event listener can attach.
Real Website Example: Wrong Cart Total
const price = "500";
const quantity = 3;
const total =
price + quantity;
console.log(total);
Output:
5003
Inspect the Types
console.log(
typeof price,
typeof quantity
);
Output:
string number
Fix the Data and Formula
const price = 500;
const quantity = 3;
const total =
price * quantity;
console.log(total);
Output:
1500
The original bug had both a type issue and the wrong operator for a cart total.
Real Website Example: Fetch Returns 404
async function loadProduct() {
const response =
await fetch(
"/api/produts/101"
);
const product =
await response.json();
console.log(product);
}
The endpoint contains a spelling mistake:
produts
Check the Network Panel
You may see:
404
Check response.ok
Improve the code:
async function loadProduct() {
const response =
await fetch(
"/api/products/101"
);
if (!response.ok) {
throw new Error(
`HTTP ${response.status}`
);
}
return response.json();
}
The Network panel helps prove whether the request itself is wrong.
Real Website Example: Form Always Shows Error
const email =
document.querySelector(
"#email"
);
if (email.value === "") {
console.log(
"Email required"
);
}
Suppose the user typed spaces.
Log:
console.log(
JSON.stringify(
email.value
)
);
You may see:
" "
Now the problem is clear.
Use:
email.value.trim() === ""
This is a good example of inspecting the real value rather than assuming it is empty.
Common Beginner Debugging Mistakes
Ignoring the Console
The browser may already be telling you exactly what failed.
Check it first.
Reading Only the Error Type
Do not stop at:
TypeError
Read the full message and location.
Changing Several Things at Once
You lose track of what fixed the issue.
Leaving Old console.log() Calls Everywhere
Temporary debugging output becomes noise.
Not Checking typeof
String-number bugs are common.
Inspect the type.
Not Checking null
DOM selectors can return null.
Ignoring Network Status Codes
A Fetch bug may actually be an API URL or server response problem.
Treating 404 Like a JSON Bug
Inspect the network request before changing your JSON code.
Swallowing Errors in Empty catch Blocks
You hide useful information.
Using alert() as the Main Debugging Tool
alert() interrupts the page and provides limited information.
Use the Console and DevTools.
Debugging Production Code First
Reproduce the issue in a development environment when possible.
Production debugging should avoid exposing sensitive data or disrupting users.
Assuming the Last Line Is the Cause
The wrong value may have been created much earlier.
Trace backward.
Fixing the Symptom Instead of the Cause
If an object property is unexpectedly missing, ask why it is missing.
Do not only add fallback text unless missing data is valid.
Forgetting to Test the Fix
A code change is not confirmed until you reproduce the original scenario again.
Not Testing Edge Cases
After fixing:
quantity = 3
also test:
quantity = 1
quantity = 0
quantity = maximum
when those values are relevant.
Best Practices for JavaScript Debugging
Open the Console before making speculative changes.
Read the full error message.
Use the file and line number.
Inspect actual values and data types.
Use clear labels in temporary logs.
Use breakpoints for complex logic.
Use the Network panel for API requests.
Use the Elements panel for DOM and CSS problems.
Use the Application panel for browser storage.
Reproduce the bug consistently.
Reduce large problems into smaller test cases.
Test one hypothesis at a time.
Use known inputs and expected outputs.
Do not hide errors without understanding them.
Remove temporary debugging code after fixing the problem.
Test the original scenario and nearby edge cases.
Beginner Exercise
Start with this broken code:
const product = {
name: "Keyboard",
price: 1500
};
console.log(
product.productName
);
const quantity = "2";
const total =
product.price +
quantity;
console.log(total);
Complete these tasks:
- Run the code.
- Inspect the output.
- Find why the product name is
undefined. - Check the type of
quantity. - Fix the product-name property.
- Convert quantity into a number.
- Calculate
price × quantity. - Confirm the final result is:
3000
Challenge Exercise
Use this HTML:
<button id="buyButton">
Buy
</button>
<p id="message">
Waiting...
</p>
Broken JavaScript:
const button =
document.querySelector(
"#buy-button"
);
const message =
document.querySelector(
"#message"
);
button.addEventListener(
"click",
() => {
message.textContent =
"Product added";
}
);
Debug it without immediately changing the selector.
Use this process:
- Check the Console.
- Log
button. - Inspect the HTML.
- Identify the mismatch.
- Fix the selector.
- Test the click again.
Extra Challenge
Debug this request:
async function loadPost() {
try {
const response =
await fetch(
"https://jsonplaceholder.typicode.com/post/1"
);
if (!response.ok) {
throw new Error(
`HTTP ${response.status}`
);
}
const post =
await response.json();
console.log(post);
} catch (error) {
console.error(error);
}
}
loadPost();
Use the Network panel to inspect the request.
Find the URL problem.
Fix it so the request uses the correct endpoint.
Frequently Asked Questions
What is JavaScript debugging?
JavaScript debugging is the process of finding why code behaves incorrectly and fixing the underlying cause.
How do I debug JavaScript in a browser?
Open Developer Tools and use panels such as:
- Console
- Sources
- Network
- Elements
- Application
Use logs, breakpoints, error messages, and runtime inspection to find the cause.
What is console.log() used for?
console.log() prints values to the browser Console.
It is useful for checking variables, calculations, function results, and code flow.
What is a breakpoint?
A breakpoint pauses JavaScript at a selected line so you can inspect the current state before execution continues.
What does debugger do in JavaScript?
The:
debugger;
statement asks DevTools to pause execution when that line is reached.
What is a JavaScript SyntaxError?
A SyntaxError means JavaScript cannot correctly parse the code.
Common causes include missing commas, brackets, braces, parentheses, or quotes.
What is a JavaScript ReferenceError?
A ReferenceError commonly means your code tried to use an identifier that is not available in the current scope.
What is a JavaScript TypeError?
A TypeError commonly occurs when code tries to perform an operation that the current value does not support.
For example, calling a method on null.
What is a JavaScript RangeError?
A RangeError occurs when a value is outside an allowed range.
Excessive recursion can cause a stack-related RangeError.
What is a stack trace?
A stack trace shows the chain of function calls that led to an error.
It helps you trace how the failing line was reached.
Why does querySelector() return null?
No element matched the selector.
Check the ID, class, selector syntax, script timing, and whether the element exists on that page.
How do I debug a click event?
First confirm the selected element exists.
Then log inside the event handler:
button.addEventListener(
"click",
() => {
console.log(
"Click detected"
);
}
);
If that works, inspect the code after the click.
How do I debug a fetch request?
Use the Network panel.
Check:
- URL
- Method
- Status
- Request body
- Response
- Headers
- CORS messages
Also check response.ok in your code.
How do I debug JSON errors?
Inspect the raw string or response before parsing.
The actual data may be invalid JSON, empty, or HTML instead.
How do I debug localStorage?
Read the raw value with getItem() and inspect the Application or storage panel in DevTools.
Check for missing keys and invalid JSON.
What is the difference between a syntax error and a logic error?
A syntax error prevents JavaScript from correctly parsing code.
A logic error uses valid syntax but produces the wrong behavior or result.
Should I use console.log() or breakpoints?
Use both.
console.log() is quick for simple value checks.
Breakpoints are better when you need to inspect changing state step by step.
What is step into?
Step into enters a called function so you can debug its internal lines.
What is step over?
Step over executes the current line without walking through every line inside a called function.
What is step out?
Step out finishes the current function and returns to its caller.
How do I find an infinite loop?
Check the loop's starting value, condition, and update.
Make sure the condition can eventually become false.
Why should I inspect data types while debugging?
A value that looks like a number may actually be a string.
That can change calculations and comparisons.
Should I use try...catch for every JavaScript error?
No.
Use try...catch when your code has a meaningful way to handle a failure.
Do not use it only to hide programming bugs.
Should I leave debugger statements in production code?
No.
Remove temporary debugger statements after you finish debugging.
Should I leave console.log() in production?
Remove temporary development logs.
Keep only logs that have a clear operational or diagnostic purpose and do not expose sensitive data.
What is the best JavaScript debugging process?
A reliable process is:
- Reproduce the problem.
- Read the error.
- Find the failing line.
- Inspect actual values.
- Trace where the wrong value came from.
- Test one fix.
- Retest the original case.
- Test nearby edge cases.
What should I learn after JavaScript debugging?
Start building small JavaScript projects.
Projects force you to combine variables, conditions, loops, functions, arrays, objects, DOM manipulation, events, forms, storage, JSON, and APIs in one working application.
Summary
JavaScript debugging helps you find the real reason code is failing.
Start with the browser Console.
Read:
- Error type
- Complete message
- File
- Line number
- Stack trace
Use:
console.log()
for quick value checks.
Use:
debugger;
or browser breakpoints when you need to pause code and inspect it step by step.
You also learned how to debug:
- Syntax errors
- Reference errors
- Type errors
- Logic bugs
- Variables
- Data types
- Conditions
- Loops
- Functions
- Arrays
- Objects
- DOM selectors
- Events
- Forms
- Fetch requests
- JSON
- localStorage
Browser DevTools gives you several useful places to investigate:
Console
Sources
Network
Elements
Application
A strong debugging process does not rely on random changes.
It reproduces the problem, gathers evidence, finds the first wrong value, tests one cause, and verifies the fix.
Continue Learning JavaScript
Previous Lesson: JavaScript localStorage Explained
Course Home: JavaScript Tutorial for Beginners
Next Lesson: JavaScript Projects for Beginners
In the next lesson, you will start combining the JavaScript skills from this course into practical projects such as a counter, to-do list, form validator, calculator, tabs, modal, shopping cart, and API-based application.
