· 10 min read
Controversial JavaScript Questions: Testing Boundaries of Developer Knowledge
JavaScript debates are part technical, part cultural. This article walks through the most contentious questions - from == vs === and semicolons to TypeScript, immutability, eval, private fields and more - explains both sides, shows examples, and offers pragmatic guidance for teams and individual developers.
Why JavaScript Sparks So Many Strong Opinions
JavaScript is unique: it’s ubiquitous, evolving fast, surprisingly flexible, and has a huge ecosystem. That combination produces a lot of legitimate technical trade-offs and a lot of opinionated preferences. Some debates are purely stylistic; others can affect correctness, performance, maintainability, and security.
Below are some of the most controversial JavaScript questions you’ll encounter. For each: the question, why people argue, the technical trade-offs, code examples, and a practical recommendation.
1) == vs === - Is type-coercing equality ever acceptable?
Question: Use == (abstract equality) or always use === (strict equality)?
Why it’s controversial: == performs type coercion and can produce surprising results; defenders say it’s concise in some cases (e.g. checking null/undefined).
Example:
0 == '0'; // true
0 === '0'; // false
null == undefined; // true
null === undefined; // falseArguments for ===:
- Avoids implicit type coercion surprises
 - Easier to reason about
 - Many style guides require it (Airbnb, etc.)
 
Arguments for ==:
- Concise checks like 
x == nullmatch bothnullandundefined - Historical codebases use it; some consider it readable in small idioms
 
Recommendation: Prefer === for clarity and correctness. If you need to check for both null and undefined, write the intention explicitly: x == null is acceptable if documented, but x === null || x === undefined or x ?? defaultValue (nullish coalescing) is clearer.
References: MDN on equality operators: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Equality_comparisons_and_sameness
2) Semicolons: Omit them or enforce them?
Question: Rely on Automatic Semicolon Insertion (ASI) or always write semicolons?
Why it’s controversial: ASI generally works, but corner cases can silently change program behavior.
Problematic example when omitting semicolons:
// Intended to return an object
function getObj() {
  return;
  {
    ok: true;
  }
}
// Because of ASI, this returns undefined, not the object.Arguments for omitting semicolons:
- Cleaner visual appearance for some
 - Modern tools and linters handle most cases
 
Arguments for requiring semicolons:
- Avoid subtle bugs from ASI
 - Uniformity across codebase
 
Recommendation: Use a consistent rule enforced by tooling (Prettier/ESLint). Many teams choose to always include semicolons to avoid rare pitfalls. See MDN on ASI: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#automatic_semicolon_insertion
3) var vs let/const - Are old habits safe?
Question: Is var still acceptable? When to use let vs const?
Why it’s controversial: var has function scoping and hoisting semantics that lead to bugs in modern code. let/const have block scoping and better intentions.
Example:
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 0);
}
// prints 3 3 3
// With let:
for (let j = 0; j < 3; j++) {
  setTimeout(() => console.log(j), 0);
}
// prints 0 1 2Arguments for let/const:
- Block scope prevents accidental leaks
 constprovides intent (value shouldn’t be reassigned)
Arguments sometimes used for var:
- Historical legacy code
 - Slightly different hoisting semantics can be used intentionally (rare)
 
Recommendation: Favor const for values that won’t be reassigned, let for variables that will. Avoid var in new code. See MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/var and https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let
4) Arrow functions vs function declarations - Which to choose for this?
Question: Use arrow functions everywhere or prefer traditional functions in some contexts?
Why it’s controversial: Arrow functions capture the surrounding this lexically, which is convenient in callbacks but unsuitable for methods that should use their own this.
Example:
const obj = {
  value: 42,
  method: () => {
    console.log(this.value); // `this` is not obj; usually undefined
  },
};
obj.method();
// vs
const obj2 = {
  value: 42,
  method() {
    console.log(this.value);
  },
};
obj2.method(); // 42Arguments for arrow functions:
- Shorter syntax
 - No need to 
bind(this)for callbacks 
Arguments for normal functions:
- Necessary for object methods using 
this - Better for constructors (cannot use arrow functions as constructors)
 
Recommendation: Use arrow functions for small callbacks and when you want lexical this. Use method shorthand or function declarations for object methods or when you need a proper this or new-constructable behavior. MDN on arrow functions: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions
5) Classes vs prototypes - Is classical OOP “better” in JS?
Question: Use ES6 class syntax or stick with prototypal patterns?
Why it’s controversial: class offers familiar OOP syntax but is mostly syntactic sugar over prototypes. Purists argue you should embrace prototypes; others prefer class for readability.
Example:
// Class syntax
class Animal {
  constructor(name) {
    this.name = name;
  }
  speak() {
    console.log(this.name + ' makes a noise');
  }
}
// Prototype equivalent
function AnimalP(name) {
  this.name = name;
}
AnimalP.prototype.speak = function () {
  console.log(this.name + ' makes a noise');
};Arguments for class:
- More familiar to developers from other languages
 - Cleaner, less boilerplate
 
Arguments for prototypes:
- Exposes the language’s original model
 - Sometimes more flexible for meta-programming
 
Recommendation: Prefer class syntax for clarity and maintainability unless you need low-level prototype manipulation. Classes are well-supported and widely understood.
6) Mutability vs Immutability - Should you avoid mutation at all costs?
Question: Is immutability always better, particularly in UI frameworks like React?
Why it’s controversial: Immutable patterns avoid shared-state bugs and make change detection easier, but copying data can be less performant and more verbose.
Example (React):
// Mutating state (bad)
state.items.push(newItem);
this.setState({ items: state.items });
// Immutable (good)
this.setState({ items: [...this.state.items, newItem] });Arguments for immutability:
- Easier reasoning about state changes
 - Simpler to implement time-travel/debugging
 - Avoids subtle shared-state bugs
 
Arguments for mutation:
- More performant in some hot loops
 - Simpler in small scripts or performance-critical code when done safely
 
Recommendation: Prefer immutability in application state (especially UI), and use mutation carefully in performance-sensitive lower-level code with clear boundaries. Libraries like Immer help balance ergonomics and immutability: https://immerjs.github.io/immer/
React docs on immutability and state updates: https://reactjs.org/docs/state-and-lifecycle.html
7) TypeScript - Salvation or Overhead? (and the any problem)
Question: Adopt TypeScript for type safety or stick with plain JavaScript?
Why it’s controversial: TypeScript adds a compile step and type system that many find increases productivity and reliability. Critics say it adds barrier-to-entry, friction for small projects, or complexity with bad typings, especially when developers overuse any.
Arguments for TypeScript:
- Catches many errors at compile time
 - Excellent editor tooling and refactoring
 - Better documentation via types
 
Arguments against TypeScript:
- Initial learning curve and build complexity
 - Overly permissive use of 
anyundermines benefits 
Recommendation: For medium+ codebases or teams, TypeScript’s benefits usually outweigh the costs. Avoid treating any as a shortcut - prefer unknown or gradual typing and add types incrementally. Official TypeScript site: https://www.typescriptlang.org/
8) Using eval and dynamic code execution - Is it ever OK?
Question: Use eval, new Function, or string-based dynamic code?
Why it’s controversial: Powerful but risky. eval runs arbitrary code so it’s a security and performance nightmare unless tightly controlled.
Arguments against eval:
- Security vulnerabilities (code injection)
 - Hard to debug and optimize
 - Most cases can be solved without it
 
Possible use cases:
- Controlled meta-programming where inputs are trusted and performance/security considered
 - Sandboxed environments with strict controls (rare)
 
Recommendation: Avoid eval and new Function unless you have compelling, audited reasons. See MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval
9) Promises vs async/await - Is one superior?
Question: Prefer .then() chains or async/await syntax?
Why it’s controversial: async/await reads like synchronous code and simplifies flow, but .then() can be more explicit for some concurrent patterns and functional chaining.
Examples:
// Promise chain
fetch(url)
  .then(r => r.json())
  .then(data => doSomething(data))
  .catch(err => console.error(err));
// async/await
async function load() {
  try {
    const r = await fetch(url);
    const data = await r.json();
    doSomething(data);
  } catch (err) {
    console.error(err);
  }
}Arguments for async/await:
- Cleaner, imperative control flow
 - Easier to follow try/catch for errors
 
Arguments for promises:
- Composability with 
Promise.all,race, and functional patterns - Non-blocking style that’s explicit about concurrency
 
Recommendation: Use async/await for readability in sequential code; use Promise.all/Promise.race or functional promise methods for explicit concurrency. Understand the event loop (microtasks vs macrotasks) to avoid subtle ordering issues: https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop
10) Modifying built-ins and monkey-patching - Dangerous convenience?
Question: Add methods to Array.prototype or other built-ins for convenience?
Why it’s controversial: Monkey-patching provides nice DSLs but breaks encapsulation, risks conflicts, and can surprise other libraries.
Example brokenness:
// Suppose some code adds a function
Array.prototype.flatten = function () {
  /* ... */
};
// Later, a library assumes standard Array methods and gets unexpected behaviorArguments for monkey-patching:
- Enables polyfills or syntactic sugar in controlled environments
 
Arguments against:
- Potential for collisions and fragile code
 - Hard to reason about in a global ecosystem
 
Recommendation: Avoid altering global prototypes. Use polyfills via vetted libraries or write small helper functions. If you must polyfill, prefer standard-compliant polyfills that check for existing behavior.
11) Private class fields (#) vs closures/TypeScript private
Question: Use native private fields (#x) or rely on closure-based privacy or TypeScript’s private modifier?
Why it’s controversial: Native private fields are enforced at runtime and not accessible externally, but their syntax and semantics differ from TypeScript’s compile-time private.
Example:
class C {
  #secret = 42;
  getSecret() {
    return this.#secret;
  }
}
const c = new C();
// c.#secret // SyntaxErrorArguments for native private fields:
- True privacy enforced by the runtime
 - No reliance on build-time checks
 
Arguments for TypeScript private:
- Familiar syntax and integrates with type system
 - Only enforces at compile time (can be bypassed in runtime)
 
Recommendation: Use native private fields when you need runtime-enforced privacy. Use TypeScript private for ergonomics across codebases if you already use TypeScript, but understand it’s only a compile-time guarantee. MDN on private fields: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Private_class_fields
12) Proxies - Miracle wrapper or footgun?
Question: Use Proxy for meta-programming and reactivity (Vue, etc.) or avoid it?
Why it’s controversial: Proxy enables powerful patterns (interception, reactivity) but can complicate debugging and produce surprising performance characteristics.
Example:
const p = new Proxy(
  {},
  {
    get(target, prop) {
      console.log('get', prop);
      return target[prop];
    },
  }
);
p.x = 10;
console.log(p.x); // logs get xArguments for Proxy:
- Enables frameworks to implement reactivity without modifying objects
 - Allows dynamic behavior and validation
 
Arguments against:
- Harder to inspect with devtools sometimes
 - Potential performance overhead in tight loops
 
Recommendation: Use Proxy where it simplifies architecture (e.g., for reactive systems), but document behavior and measure performance when used in hot paths. MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy
13) Frameworks vs Vanilla JS - How much library is too much?
Question: Build with a framework (React/Vue/Angular) or use vanilla JS (or micro-libraries)?
Why it’s controversial: Frameworks accelerate development and solve complex UI problems, but they add load, lock-in, and cognitive overhead. Vanilla JS is lighter but you’ll reimplement framework features yourself.
Considerations:
- Project complexity and team skill set
 - Long-term maintenance and upgrade paths
 - Performance and bundle size constraints
 
Recommendation: Choose based on needs. For complex interactive UIs, frameworks are usually justified. For small widgets, consider vanilla or micro-libraries. Always evaluate cost of maintenance and developer familiarity.
14) Tooling and style: Prettier/ESLint vs personal style
Question: Let automatic formatters and opinionated linters enforce style, or keep personal preferences?
Why it’s controversial: Developers can be attached to style choices (tabs vs spaces, semicolons, trailing commas). Tooling removes bikeshedding and maintains consistency, but some feel it’s a loss of control.
Recommendation: Use automated formatting (Prettier) and a shared linting configuration (ESLint) to reduce friction during code review. Decide on project-wide rules and commit them to version control. This reduces subjective debates and frees reviews to focus on logic.
Prettier: https://prettier.io/ ESLint: https://eslint.org/
15) Guardrails for controversial choices - Pragmatic team strategies
- Adopt a style guide and automate it (Prettier + ESLint + CI). This removes low-signal debates.
 - Prefer clarity and explicitness in shared code; optimize later with measurements.
 - Use type systems (TypeScript) for medium+ codebases, and avoid 
anyas a default. - Write tests that encode expectations around corner-case behaviors (e.g., equality, null handling).
 - Keep controversial code isolated and documented (e.g., mutation hotspots, performance-critical loops).
 - Encourage regular knowledge-sharing: code reviews, brown-bags, and RFC processes for architectural changes.
 
Final thoughts - Trade-offs over dogma
Most JavaScript controversies come down to trade-offs between safety, ergonomics, performance, and historical baggage. There are few absolute right answers. The healthier approach is to:
- Understand the technical trade-offs (not just the aesthetics)
 - Make team-level decisions and encode them in tooling
 - Measure performance/security when claims are made
 - Prefer clarity for future readers of your code
 
If you walk away with one rule: be explicit about intent. When intent is explicit, many controversies dissolve into well-reasoned design choices.
Further reading and references
- MDN: Equality comparisons and sameness - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Equality_comparisons_and_sameness
 - MDN: Automatic Semicolon Insertion - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#automatic_semicolon_insertion
 - MDN: var, let, const - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/var
 - MDN: Arrow functions - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions
 - MDN: Private class fields - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Private_class_fields
 - MDN: Proxy - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy
 - React docs (state & lifecycle) - https://reactjs.org/docs/state-and-lifecycle.html
 - TypeScript - https://www.typescriptlang.org/
 - Prettier - https://prettier.io/
 - ESLint - https://eslint.org/
 


