Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more

When JavaScript behaves unexpectedly, start with the exact error and the value or type at the point of failure. Many common bugs come from small spelling or syntax slips, a mismatch between the value you expect and the value you have, or asynchronous work that has not finished yet. The fixes below distinguish actual syntax errors from valid JavaScript that produces surprising results.

1. A name is undefined or the script reports a syntax error

Check the runtime’s full message and the indicated line before assuming the problem is complicated. JavaScript identifiers are case-sensitive: getElementById() and getElementsById() are different names. A misspelled variable, API name, or capitalization mismatch can make a valid intended name appear unavailable. Punctuation can also change meaning; a semicolon inside a quoted style value is part of the string, not a statement terminator. Error wording varies by runtime.

Compare each name with its declaration and the API spelling, then inspect nearby punctuation. Reduce the failing code to the smallest example you can reproduce. MDN’s troubleshooting guidance covers spelling, casing, syntax, and other frequent error sources: MDN JavaScript debugging.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

2. A comparison says two values are equal when they should not be

== may convert operands before comparing them; === compares without converting different types. For example, 1 == "1" is true, while 1 === "1" is false. Loose equality has defined behavior, so using it is not a syntax error, but implicit conversion can make conditions harder to reason about.

if (input == 1) {
  // A string "1" also passes this comparison.
}

For ordinary comparisons, prefer strict equality and inequality. If conversion is intended, make it explicit and validate the result:

const numericInput = Number(input);
if (Number.isFinite(numericInput) && numericInput === 1) {
  // Conversion and validation are visible.
}

MDN recommends strict equality in its code examples and explains the equality operators’ behavior: Equality comparisons and sameness and MDN JavaScript code style.

3. A variable has the wrong value inside a loop or callback

var is scoped to a function, not a block. In a loop, callbacks that run later can therefore share the same changing binding and observe its final value. let and const are block scoped; a let declared in a loop header gives each iteration its own binding.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
const callbacks = [];
for (var i = 0; i < 3; i++) {
  callbacks.push(() => i);
}
// Each callback reads the same var binding.
const callbacks = [];
for (let i = 0; i < 3; i++) {
  callbacks.push(() => i);
}
// Each callback reads its iteration's binding.

Use const when a binding will not be reassigned and let when reassignment is part of the design. A const binding cannot be reassigned, but an object or array assigned to it can still be mutated. Lexical declarations also cannot be accessed before initialization; doing so within their scope’s temporal dead zone throws an error. See MDN’s guide to declarations.

4. A value is undefined, null, or unexpectedly skipped

undefined can mean a declared variable was never assigned, a function returned without a value, or an object lacks the requested property. null is a separate value. Trying to access a property or call a method on either can throw a TypeError.

function findUser() {
  // No return statement: the result is undefined.
}

const user = findUser();
// user.name cannot be read from undefined.

Check the value your code’s contract allows, rather than treating every falsy value as missing. A condition such as if (!value) also rejects valid values like 0, false, and "". When both null and undefined mean “absent,” a nullish check is explicit:

if (value == null) {
  // Matches null or undefined, but not 0, false, or "".
}

Use optional chaining, such as user?.name, only when skipping access for a nullish user is an acceptable result. It does not repair a missing value if the program needs one. MDN explains undefined and optional chaining.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

5. An async function returns a Promise instead of the value

Every call to an async function returns a Promise, even when the function returns an ordinary value. If downstream code needs the eventual result, await the Promise in an allowed context or return it so the caller can handle it.

async function getCount() {
  return 3;
}

const count = getCount(); // A Promise, not the number 3.
async function showCount() {
  const count = await getCount();
  console.log(count);
}

In a regular script, await must be inside an async function; top-level await is available in modules. Awaited failures should be handled with try/catch, or the Promise chain should have a rejection handler.

async function loadData() {
  try {
    return await fetchData();
  } catch (error) {
    // Handle or report the failure.
    throw error;
  }
}

For independent operations, start them and coordinate their outcomes together rather than needlessly waiting one at a time. Promise.all rejects if a member rejects; Promise.allSettled reports each operation’s outcome so successes and failures can be inspected individually.

const results = await Promise.all([loadProfile(), loadSettings()]);

const outcomes = await Promise.allSettled([loadProfile(), loadSettings()]);

With already-started Promises, promptly connect them to a combined handler: sequentially awaiting an array can leave a later rejection unhandled while execution waits for an earlier Promise. MDN documents async functions, await, and using Promises.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

6. A callback uses the wrong this

A regular function’s this depends on how it is called, not simply on where it was written. Passing an object method as a callback may therefore lose the receiver you expected. Arrow functions behave differently: they capture this from their surrounding lexical scope. Use one when that is the intended behavior, not as a universal replacement for methods or callbacks that need their own receiver.

const counter = {
  value: 0,
  incrementLater() {
    setTimeout(function () {
      this.value++; // This callback's receiver may not be counter.
    }, 0);
  }
};
const counter = {
  value: 0,
  incrementLater() {
    setTimeout(() => {
      this.value++; // The arrow captures this from incrementLater.
    }, 0);
  }
};

If a regular callback needs a particular receiver, bind it or pass the receiver explicitly. Keep loop bindings scoped per iteration as well when each delayed callback needs its own value. MDN explains closures and this.

7. A bug survives because debugging is guesswork

Use browser developer tools to inspect the error and the actual values at runtime. A linter such as ESLint can flag selected problematic patterns, but it cannot prove the logic is correct or that every runtime input is safe.

  1. Read the complete error and inspect the indicated line.
  2. Check spelling, capitalization, and whether each name is in scope.
  3. Inspect values and types immediately before the failure.
  4. Determine whether a value is synchronous, a Promise, null, or undefined.
  5. Reduce the issue to a minimal example, change one thing, and reproduce the original case.
  6. Run a linter and relevant tests, then verify the behavior at runtime.

MDN’s debugging guide covers browser tools and troubleshooting; ESLint’s official site describes the linter.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.