· 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; // false
Arguments for ===
:
- Avoids implicit type coercion surprises
- Easier to reason about
- Many style guides require it (Airbnb, etc.)
Arguments for ==
:
- Concise checks like
x == null
match bothnull
andundefined
- 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 2
Arguments for let
/const
:
- Block scope prevents accidental leaks
const
provides 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(); // 42
Arguments 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
any
undermines 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 behavior
Arguments 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 // SyntaxError
Arguments 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 x
Arguments 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
any
as 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/