In TypeScript, the equals operator == (loose equality) converts both values to a common type before it compares them, while the strict equals operator === (strict equality) returns true only when both the type and the value match. So “10” == 10 is true and “10” === 10 is false. The rule for everyday code is to use === and !==, with one accepted exception: value == null, which checks for null and undefined at once.
Both operators exist in TypeScript because they come from JavaScript, and they behave identically at runtime. The difference TypeScript makes is at compile time: it refuses many == comparisons between unrelated types, but not all of them. This page explains the conversion rules of == with real results, shows where the compiler protects us and where it cannot, and covers the special cases NaN, -0 and objects. The values in the comments were produced with TypeScript 7.0.2 and Node.js 22.
The values in the first snippet have the type unknown, the way data arrives from JSON.parse() or an API. TypeScript allows both operators on such values, so the runtime difference shows directly.
const input: unknown = JSON.parse('"10"'); // the string "10"
// 1. == converts the string, === does not
const loose = input == 10; // loose = true
const strict = input === 10; // strict = false
// 2. == null matches null and undefined
const ages = new Map<string, number>([["Lokesh", 37]]);
const raj = ages.get("Raj"); // raj = undefined
const isMissing = raj == null; // isMissing = true
const isNull = raj === null; // isNull = false
// 3. NaN equals nothing, Object.is() finds it
const nan = Number("abc"); // nan = NaN
const strictNan = nan === nan; // strictNan = false
const sameNan = Object.is(nan, NaN); // sameNan = true
// 4. Objects compare by reference with both
const lokesh = { age: 37 };
const copy = { age: 37 };
const equal = lokesh == copy; // equal = false
1. Results of == and === Side by Side
The quickest way to see the difference is to run both operators on the same pairs of values. Every row where the two columns differ is a case where == converted a value before comparing. The != and !== operators return the opposite of == and === and follow the same rules.
| Values | == | === |
|---|---|---|
| “10” and 10 | true | false |
| 0 and “” | true | false |
| “0” and “” | false | false |
| 0 and false | true | false |
| “1” and true | true | false |
| null and undefined | true | false |
| null and 0 | false | false |
| NaN and NaN | false | false |
| [5] and “5” | true | false |
The === column is false in every row. Each pair mixes two types, except the NaN row, which section 5 explains. The == column is harder to predict, and that is the main argument against it: the reader of the code has to know the conversion rules to know the result.
2. How == Converts Values
The ECMAScript specification describes == as the IsLooselyEqual algorithm. When the two types are the same, it behaves like ===. When they differ, it converts one or both values and compares again. Simplified, the steps are:
- null and undefined are equal to each other and to nothing else.
- A boolean is converted to a number first: true becomes 1 and false becomes 0.
- When a string is compared with a number, the string is converted to a number. An empty string becomes 0.
- An object compared with a primitive is converted to a primitive, usually a string through toString(). The array [5] becomes “5”.
- NaN is not equal to anything, including itself.
const text: unknown = "10";
const empty: unknown = "";
const yes: unknown = true;
const list: unknown = [5];
const nothing: unknown = null;
// 1. String vs number: the string becomes a number
const r1 = text == 10; // r1 = true
// 2. Boolean: becomes 1 or 0 first
const r2 = yes == "1"; // r2 = true
// 3. The empty string becomes 0
const r3 = empty == 0; // r3 = true
// 4. Object: converted to a primitive first
const r4 = list == 5; // r4 = true
// 5. null equals only null and undefined
const r5 = nothing == 0; // r5 = false
const r6 = nothing == undefined; // r6 = true
// 6. Not transitive
const r7 = empty == "0"; // r7 = false
The last line shows why the rules cause bugs. The empty string equals 0, and “0” equals 0, but the empty string does not equal “0”, because two strings are compared as strings without conversion. In mathematics, equality is transitive: if a equals b and b equals c, then a equals c. Loose equality does not have that property.
3. What the TypeScript Compiler Checks
TypeScript knows the type of each operand, and it reports a comparison between types that have no value in common. This check applies to == and === alike, so in typed code most loose-equality surprises never reach runtime.
const age = 37;
const input = "37";
const same = age == input;
const flag = age == true;
src/err.ts(3,14): error TS2367: This comparison appears to be unintentional because the types 'number' and 'string' have no overlap.
src/err.ts(4,14): error TS2367: This comparison appears to be unintentional because the types 'number' and 'boolean' have no overlap.
The check has limits. It does not apply in two cases:
- One side is any or unknown, which is the type of parsed JSON, form data and many library results.
- The types overlap, for example a string | number union compared with a number.
In those cases, == compiles and converts at runtime.
const age = 37;
const input = "37";
// 1. Convert explicitly, then use ===
const same = Number(input) === age; // same = true
// 2. A union type still allows ==
const ids: (string | number)[] = ["1", 1];
const id = ids[0];
const loose = id == 1; // loose = true
const strict = id === 1; // strict = false
const byText = String(id) === "1"; // byText = true
The fix is the same in both cases: convert the value on purpose with Number() or String(), then compare with ===. The conversion is visible in the code, and the result does not depend on the coercion rules. For unknown values, a typeof check before the comparison also narrows the type, as described in the TypeScript Handbook chapter on narrowing.
4. The == null Check
Rule 1 from section 2 makes value == null a short, exact test for “null or undefined“. It matches nothing else: not 0, not “”, not false. Many style guides and the ESLint eqeqeq rule allow this one use of ==.
const ages = new Map<string, number>([["Lokesh", 37]]);
const raj = ages.get("Raj"); // type number | undefined
// 1. One check for null and undefined
const missing1 = raj == null; // missing1 = true
const missing2 = raj === null || raj === undefined; // missing2 = true
// 2. != null narrows the type
const lokesh = ages.get("Lokesh");
if (lokesh != null) {
console.log(lokesh + 1); // 38
}
TypeScript understands the check: inside the if block, the type of lokesh is number, so the addition compiles. Teams that prefer only strict operators write the longer missing2 form. To keep the null exception in a project that enforces ===, the ESLint setting is [“error”, “always”, { “null”: “ignore” }]. The undefined vs null article explains when each of the two values appears.
5. NaN, -0 and Object.is()
Strict equality has two exceptions of its own:
- NaN is not equal to itself, even with ===.
- The two zeros 0 and -0 are equal even though they are different values in floating-point math.
The Object.is() method compares with the “SameValue” rule, which fixes both.
const nan = Number("abc");
// 1. NaN
const r1 = nan === nan; // r1 = false
const r2 = Number.isNaN(nan); // r2 = true
const r3 = Object.is(nan, NaN); // r3 = true
// 2. Positive and negative zero
const r4 = 0 === -0; // r4 = true
const r5 = Object.is(0, -0); // r5 = false
In practice, we test for NaN with Number.isNaN() and keep === for everything else. Object.is() is useful in generic code, such as a cache or a change detector, that must treat NaN as equal to NaN.
6. Objects, Arrays and Dates
For objects, arrays, dates and functions, == and === give the same answer: true only when both sides refer to the same object in memory. Neither operator looks at the contents.
const a = { name: "Lokesh" };
const b = a;
const c = { name: "Lokesh" };
const r1 = a == b; // r1 = true, same object
const r2 = a == c; // r2 = false
const r3 = a === c; // r3 = false
const r4 = a.name === c.name; // r4 = true
To compare contents, we compare the fields that define equality, as in r4. For two Date objects, we compare getTime() values. In Node.js, isDeepStrictEqual() from the node:util module compares nested objects and arrays. More cases, including string and date order, are in TypeScript comparison operators.
7. Which Operator to Use
Strict equality is the default in TypeScript code, because its result depends only on the two values and never on hidden conversions. Five rules cover nearly every comparison in an application.
- Use === and !== for all comparisons of numbers, strings, booleans and literal types.
- Use value == null or value != null when null and undefined mean the same thing, or write both strict checks if the team bans ==.
- Convert input from forms, URLs and JSON with Number() or String() before comparing it, instead of relying on ==.
- Use Number.isNaN() to test for NaN, and Object.is() only when NaN or -0 must be handled as values.
- Compare object fields, or a deep-equality helper, when two objects should count as equal by content.
The ESLint rule eqeqeq enforces this list automatically in a project, so a stray == fails the lint step instead of reaching code review.
8. Running the Equality Examples
Every snippet that compiles is in the equals-vs-strict-equals folder on GitHub, together with the code that prints the table from section 1. The project pins TypeScript 7.0.2 and was tested on Node.js 22.
npm install
npm start
9. Conclusion
The == operator converts types by a set of rules that are hard to remember and not even transitive, while === compares type and value without conversion. TypeScript blocks the worst loose comparisons with error TS2367, but values typed any, unknown or a union still reach == at runtime. We write === everywhere, keep == null as the single exception when the team allows it, convert input explicitly, and use Number.isNaN() or Object.is() for the special numeric cases.
10. References
The exact conversion algorithm is in the ECMAScript specification; MDN explains the same rules with more examples.
- MDN: Equality comparisons and sameness
- MDN: Equality (==)
- MDN: Strict equality (===)
- ECMAScript specification: IsLooselyEqual
- ESLint: eqeqeq
- TypeScript Handbook: Narrowing
Happy Learning !!