10 Oddities And Secrets About Javascript

Master the surprising quirks and hidden features that separate competent JavaScript developers from true experts. From null being an object to optional chaining mastery.

JavaScript. At once bizarre and yet beautiful, it is surely the programming language that Pablo Picasso would have invented. Null is apparently an object, an empty array is apparently equal to false, and functions are bandied around as though they were tennis balls.

This article is a collection of JavaScript's most curious oddities and well-kept secrets. Some sections will give you insight into how these curiosities can be useful to your code, while others are pure "what the heck" material that you simply need to understand to avoid bugs. Let's get started.

These fundamentals pair well with our guide on Understanding TypeScript Generics, where you'll learn how strong typing can catch many of the type-related quirks we explore here.

Data Types And Definitions That Defy Logic

Understanding JavaScript's type system is foundational to mastering the language. These oddities aren't just trivia--they're the source of many bugs and the key to writing predictable code.

Null Is an Object

Let's start with everyone's favorite JavaScript oddity. Null is technically an object--which, as far as contradictions go, is right up there with the best of them. Null? An object? "Surely, the definition of null is the *total absence of meaningful value," you say. You'd be right. But that's the way it is.

The proof:

console.log(typeof null); // alerts 'object'

Despite this, null is not considered an instance of an object. If null is the absence of value, then it obviously can't be an instance of anything. Hence, the following evaluates to false:

console.log(null instanceof Object); // evaluates false

Why this matters: This quirk affects how you perform type checking. When checking for null values, use strict equality (=== null) rather than typeof checks. Understanding this distinction prevents bugs in conditional logic and API response handling.

As noted by the experts at Smashing Magazine, this historical bug has been preserved for backward compatibility despite years of community feedback.

NaN Is a Number

You thought null being an object was ridiculous? Try dealing with the idea of NaN -- "not a number" -- being a number! Moreover, NaN is not considered equal to itself. Does your head hurt yet?

console.log(typeof NaN); // alerts 'Number'
console.log(NaN === NaN); // evaluates false

NaN is the only value in JavaScript that is not equal to itself. The only reliable way to confirm that something is NaN is via the isNaN() function or the more precise Number.isNaN():

console.log(isNaN(NaN)); // true
console.log(Number.isNaN(NaN)); // true

Common scenarios: NaN appears in mathematical operations that don't produce valid numbers, such as 0/0, parseInt('hello'), or Math.sqrt(-1). Proper validation prevents NaN from propagating through your calculations.

According to Smashing Magazine's analysis, this behavior stems from IEEE 754 floating-point standard implementation, which JavaScript follows for all numeric operations.

The Truthy and Falsy Mystery

Here's another much-loved JavaScript oddity:

console.log(new Array() == false); // evaluates true

To understand what's happening, you need to understand truthy and falsy values. In JavaScript, every non-boolean value has a built-in boolean flag that's called when the value is asked to behave like a boolean.

When JavaScript compares values of differing data types, it first "coerces" them into a common type. The six falsy values are:

  • false
  • 0 (zero)
  • "" (empty string)
  • null
  • undefined
  • NaN

Everything else is truthy--including empty arrays and objects!

The confusing case of empty arrays:

var someVar = []; // empty array
console.log(someVar == false); // evaluates true
if (someVar) {
 console.log('hello'); // this runs, so someVar evaluates to true
}

The solution: Use strict equality (===) to avoid coercion:

console.log(0 === false); // false - zero is a number, not a boolean

Understanding these coercion rules is critical for debugging and writing reliable conditional logic in your web development projects. Pair this knowledge with our CSS Tips And Techniques guide to master both JavaScript behavior and styling best practices.

Regular Expressions Beyond the Basics

Many developers get by just on match() and replace() with regular expressions. But JavaScript defines more powerful methods that many never discover. Regular expressions are essential for implementing search functionality, input validation, and content filtering in your applications, which are core components of our web development services.

Replace Can Accept a Callback Function

This is one of JavaScript's best-kept secrets. Most usages of replace() look like this:

alert('10 13 21 48 52'.replace(/\d+/g, '*')); // replace all numbers with *

But what if you wanted more control? What if you wanted to replace only numbers under 30? This can't be achieved with regular expressions alone. You need to jump into a callback function:

alert('10 13 21 48 52'.replace(/\d+/g, function(match) {
 return parseInt(match) < 30 ? '*' : match;
}));
// Output: * * 48 52

For every match, JavaScript calls our function, passing the match as the first argument. We return either the asterisk (if the number is under 30) or the match itself (no replacement).

The callback receives these arguments:

  • match - the matched substring
  • p1, p2, ... - captured groups (if any)
  • offset - the position of the matched substring
  • string - the entire string being examined

This technique, documented by Smashing Magazine, is invaluable for data sanitization, content transformation, and building flexible text processing pipelines.

Regular Expressions: More Than Just Match and Replace

Of particular interest is test(), which works like match() except that it doesn't return matches--it simply confirms whether a pattern matches. It's computationally lighter:

console.log(/\w{3,}/.test('Hello')); // alerts 'true'

The RegExp constructor enables dynamic regular expressions, as opposed to static ones. With regular literal syntax (/pattern/), you can't reference variables. With RegExp(), you can:

function findWord(word, string) {
 var instancesOfWord = string.match(new RegExp('\\b' + word + '\\b', 'ig'));
 alert(instancesOfWord);
}
findWord('car', 'Carl went to buy a car but had forgotten his credit card.');
// Returns: ['car'] - ignores 'Carl' and 'card'

The \\b creates word boundaries on either side of the word, ensuring we match whole words only.

This pattern is essential for implementing search functionality, input validation, and content filtering in your applications.

Functions And Scope Secrets

JavaScript's function system is surprisingly flexible. These features demonstrate the power--and complexity--of the language's execution model.

You Can Fake Scope with call() and apply()

The scope in which something executes defines what variables are accessible. JavaScript's this keyword always points to the current execution context--but we can con our function into thinking it's running in a different scope:

var animal = 'dog';
function getAnimal(adjective) {
 console.log(adjective + ' ' + this.animal);
}
getAnimal('lovely'); // alerts 'lovely dog'

Using call(), we can make this function run on a different object:

var myObj = { animal: 'camel' };
getAnimal.call(myObj, 'lovely'); // alerts 'lovely camel'

Here, our function runs on myObj instead of window--specified as the first argument of the call method. Any subsequent arguments are passed to our function.

apply() does the same job, except that arguments are specified as an array:

getAnimal.apply(myObj, ['lovely']); // same result, args as array

Practical use cases:

  • Borrowing methods from other objects
  • Array-like object iteration (using Array.prototype methods on arguments)
  • Setting this in event handlers

This technique, as covered by Smashing Magazine, demonstrates JavaScript's prototypal nature and enables powerful code reuse patterns.

Functions Can Execute Themselves

There's no denying it:

(function() {
 console.log('hello');
})(); // alerts 'hello'

This is an Immediately Invoked Function Expression (IIFE). The syntax is simple: declare a function and immediately call it.

One good use: binding current values for callbacks. Consider this common problem:

var someVar = 'hello';
setTimeout(function() {
 console.log(someVar);
}, 1000);
var someVar = 'goodbye';
// After 1 second: alerts 'goodbye' (not 'hello')

The timeout callback runs later, when someVar has already been overwritten. IIFEs provide a solution:

var someVar = 'hello';
setTimeout((function(someVar) {
 return function() {
 console.log(someVar);
 };
})(someVar), 1000);
var someVar = 'goodbye';
// After 1 second: alerts 'hello' - the captured value!

This is like taking a photo before you respray the car--the photo will forever show the color at the time it was taken. This pattern is the foundation of module patterns and modern bundler output.

IIFEs remain relevant today, though modern JavaScript provides cleaner alternatives like let block scoping and modules. Understanding this pattern helps you read legacy code and debug closure-related issues.

Modern JavaScript Secrets

These features represent the evolution of JavaScript into a more expressive and developer-friendly language. Understanding these modern patterns is essential for any developer working with contemporary frameworks and libraries. When building complex applications, combining these techniques with AI-powered development approaches can significantly boost productivity.

The Void Operator

You've probably seen void(0) or javascript:void(0) in links. The void operator evaluates an expression but always returns undefined.

const result = void console.log('side effect');
console.log(result); // undefined

Modern uses:

  • Preventing a function's return value from being used
  • Creating undefined primitives (historically important, now less so)
  • The classic <a href="javascript:void(0)"> pattern (though event.preventDefault() is preferred today)
function surprise() {
 console.log('This function does not return anything.');
 return 42;
}
const result = void surprise();
console.log(result); // undefined - the 42 is discarded

As noted by developers on DEV Community, this operator is rarely needed today but understanding it helps when maintaining older codebases.

The Double Bang (!!) Technique

Sure, Boolean() converts values to true or false, but the double bang (!!) is the concise shortcut. The first ! negates the value, and the second ! negates it again--effectively converting it to a Boolean:

const name = 'Alice';
const hasName = !!name; // true - non-empty string is truthy

const empty = '';
const hasEmpty = !!empty; // false - empty string is falsy

const zero = 0;
const hasZero = !!zero; // false - zero is falsy

const user = null;
const hasUser = !!user; // false - null is falsy

Common patterns:

// Checking if a value exists
const isLoggedIn = !!userId;

// Converting for API payloads
const payload = {
 active: !!isEnabled,
 count: items.length || 0
};

This technique, popular among modern JavaScript developers, is widely used in production code for its brevity and clarity.

Symbols: Creating Unique Property Keys

Symbol is a unique and immutable primitive type, often used for creating "hidden" keys for object properties. When you need to ensure a property name won't accidentally collide with another, Symbols are your secret weapon:

const uniqueKey = Symbol('myUniqueKey');
const obj = {
 [uniqueKey]: 'This is a secret value'
};
console.log(obj[uniqueKey]); // "This is a secret value"

Key properties of Symbols:

  • Each Symbol() call creates a unique value
  • Symbols are not enumerable in for...in loops
  • Symbols don't show up in Object.keys() or JSON.stringify()
  • Symbol.for() creates shared symbols in a global registry

Well-known Symbols: JavaScript provides built-in Symbols like Symbol.iterator (for iterables) and Symbol.toStringTag (for custom class identification):

class MyClass {
 get [Symbol.toStringTag]() {
 return 'MyCustomClass';
 }
}
console.log(Object.prototype.toString.call(new MyClass()));
// "[object MyCustomClass]"

This feature, highlighted by DEV Community contributors, is essential for implementing custom iterables and avoiding property name collisions in larger codebases. For more on organizing your code and avoiding naming conflicts, explore our guide on Battling BEM.

Optional Chaining (?.) for Safer Property Access

The optional chaining operator (?.) simplifies deep property access and prevents "Cannot read property of undefined" errors:

// Before optional chaining
const street = user && user.address && user.address.street;

// With optional chaining
const street = user?.address?.street; // undefined if any link is null/undefined

Works with methods too:

// Call method only if it exists
user.notify?.();

// Array access with optional chaining
const firstItem = users?.[0];

Short-circuiting behavior: If the value before ?. is null or undefined, the entire expression short-circuits and returns undefined.

This is essential for:

  • Handling API responses with optional fields
  • Accessing nested object properties safely
  • Calling methods that may not exist on all objects
  • Reducing conditional boilerplate code

As noted by modern JavaScript experts, optional chaining has become a staple of robust application development.

Performance Considerations

Understanding these oddities leads to better performance. Here's what matters:

Avoid Unnecessary Type Coercion

Using === instead of == consistently prevents unexpected type conversions and improves performance. Modern JavaScript engines optimize strictly-typed comparisons more effectively.

Understanding Closure Implications

IIFEs and closures capture variables by reference. Be mindful of memory implications when creating many functions in loops. Use block scoping (let/const) where appropriate.

When Bitwise Operations Help

Bitwise operations convert to 32-bit integers, which can be useful for:

  • Fast rounding: ~~value truncates to integer
  • Color manipulation: color & 0xff extracts RGB channels
  • Bit flags: storing multiple boolean values in one integer

Modern JavaScript Engine Optimizations

V8 (Chrome/Node) and other modern engines optimize heavily for common patterns. Using idiomatic JavaScript often yields the best performance without micro-optimizations. These optimizations, combined with proper web development practices, ensure your applications run smoothly. For applications requiring optimal search visibility, our SEO services complement these performance best practices with comprehensive optimization strategies.

By understanding these performance implications alongside the quirks we've explored, you'll write JavaScript code that is both elegant and efficient.

Frequently Asked Questions

Why does typeof null return 'object'?

This is a historical bug in JavaScript that has been preserved for backward compatibility. In the original implementation, values were stored as a 32-bit integer with a type tag. The type tag for objects was 0, and null was represented as a null pointer (all zeros), which matched the object tag. Despite this, null is not considered an instance of Object.

Should I use == or === for comparisons?

Use `===` (strict equality) in almost all cases. It compares both value and type without coercion, leading to more predictable code. Reserve `==` (loose equality) for specific cases where you intentionally want type coercion.

When should I use Symbols over regular property keys?

Use Symbols when you need: (1) guaranteed unique keys that won't conflict with other code, (2) "hidden" properties that won't show up in normal enumeration, (3) to define well-known interfaces like Symbol.iterator for custom iterables.

What is the difference between isNaN() and Number.isNaN()?

The global `isNaN()` coerces its argument to a number first, so `isNaN('hello')` returns true. `Number.isNaN()` only returns true for actual NaN values. Always prefer `Number.isNaN()` for precise checking.

Master Modern JavaScript for Better Web Development

Understanding JavaScript's quirks and features makes you a more effective developer. Whether you're building with Next.js, React, or vanilla JavaScript, these fundamentals apply across all modern web development.

Sources

  1. Smashing Magazine: 10 Oddities And Secrets About JavaScript - The definitive original article covering 10 core JavaScript oddities with code examples
  2. DEV Community: JavaScript SECRETS - Become a coding wizard! - Modern developer perspective on lesser-known JavaScript features