null vs undefined in JavaScript
Short answer
undefined is what JavaScript gives you when a value was never set — an uninitialised variable, a missing property, a function with no return. null is a value you assign yourself to say "deliberately empty". The engine never produces null on its own; every null in your program was written by someone.
| undefined | null | |
|---|---|---|
| Who produces it | The JavaScript engine | You, explicitly |
| Means | Never assigned | Deliberately empty |
| typeof | "undefined" | "object" — a famous bug |
| == each other | true | true |
| === each other | false | false |
| In JSON | Dropped from objects, null in arrays | Preserved as null |
| Triggers default parameters | Yes | No |
| Number() gives | NaN | 0 |
| Is it a keyword | No — a global property | Yes — a literal |
The difference in one sentence
undefined is the absence of a value. null is the presence of a value that represents nothing. That sounds like wordplay until you look at where each one comes from: JavaScript hands you undefined automatically in six or seven situations, and it never hands you null. If a variable holds null, some human decided it should.
1let x;
2x; // → undefined declared, never assigned
3
4const obj = { a: 1 };
5obj.missing; // → undefined property that isn't there
6
7[1, 2, 3][10]; // → undefined index out of range
8
9function noReturn() {}
10noReturn(); // → undefined no return statement
11
12function withParam(p) { return p; }
13withParam(); // → undefined argument not passed
14
15void 0; // → undefined the void operator, always
16
17// And the only way to get null:
18const deliberatelyEmpty = null; // someone typed itThat asymmetry is the answer interviewers are listening for. "undefined is the language saying it has nothing for you; null is the programmer saying there is nothing here on purpose." Everything else on this page is a consequence of that distinction.
Why typeof null is "object"
This is the single most-asked follow-up on the topic, and the honest answer is that it is a bug from 1995 that can never be fixed. In the first implementation of JavaScript, values were stored as a type tag plus a payload. The tag for objects was 000, and null was represented as the null pointer — all zero bits — so `typeof` read the tag as 000 and reported "object".
1typeof null; // → "object" wrong, and permanent
2typeof undefined; // → "undefined" correct
3
4// So this common guard silently lets null through:
5function process(value) {
6 if (typeof value === "object") {
7 return Object.keys(value); // → TypeError when value is null
8 }
9}
10
11// Correct checks:
12value === null; // → the only reliable null test
13Object.prototype.toString.call(null); // → "[object Null]"
14value === null || value === undefined; // → both, explicitly
15value == null; // → both, in one operatorA fix was proposed for ES6 — making `typeof null` return "null" — and it was rejected, because an unknown quantity of existing code branches on the current behaviour. The web's compatibility guarantee means some mistakes are permanent, and being able to say that calmly is worth more than memorising the bit pattern.
== and === with null and undefined
This is the one place where loose equality has a genuinely useful, well-defined behaviour rather than being a trap. The specification says null and undefined are loosely equal to each other and to nothing else. Not to 0, not to "", not to false. So `x == null` is a precise, complete check for "is this null or undefined" — and nothing else slips through.
1null == undefined; // → true
2null === undefined; // → false
3
4null == 0; // → false ← surprising, but by design
5null == false; // → false
6null == ""; // → false
7null >= 0; // → true ← relational ops DO coerce; == does not
8
9undefined == 0; // → false
10undefined == false; // → false
11undefined == NaN; // → false
12
13// Which makes this idiom safe and precise:
14if (value == null) {
15 // runs for null and undefined ONLY
16}
17
18// The verbose equivalent most style guides used to insist on:
19if (value === null || value === undefined) {}The `null >= 0` line is worth understanding rather than memorising. Relational operators convert their operands to numbers, and Number(null) is 0, so the comparison becomes 0 >= 0. Loose equality has a special-case rule that skips that conversion for null. Two different algorithms, which is exactly why `null == 0` is false while `null >= 0` is true.
?? and ?. — the operators built for this distinction
Nullish coalescing and optional chaining both trigger on exactly null and undefined, and on nothing else. That is what makes them different from `||` and from a truthiness check, and it is why they fixed a whole category of bug that used to be common: falsy values that are legitimate data.
1const count = 0;
2const name = "";
3
4count || 10; // → 10 wrong: 0 is a real value
5count ?? 10; // → 0 correct
6
7name || "Guest"; // → "Guest" wrong if the user cleared the field
8name ?? "Guest"; // → "" correct
9
10// The classic version of this bug:
11function render({ showCount = true, count }) {
12 const displayed = count || "N/A"; // → "N/A" when count is genuinely 0
13}
14
15// Optional chaining, same rule:
16const user = { profile: null };
17user.profile?.city; // → undefined, no throw
18user.profile.city; // → TypeError: Cannot read properties of null
19
20user.getName?.(); // → undefined if getName isn't a function
21arr?.[0]; // → undefined if arr is null or undefinedThis is 1 of 80+ questions in the JavaScript Interview Kit
Get every question with detailed answers, follow-ups and real code — plus coding challenges and a last-minute revision sheet. One-time payment, instant access.
⚡ Get the JavaScript Interview Kit → ₹299Default parameters only fire on undefined
Default parameter values and destructuring defaults both trigger on undefined and never on null. This is deliberate and consistent with the rest of the language: the default is for "nothing was passed", and passing null is passing something. It also catches people constantly, because an API that returns null for a missing field will not trigger your defaults.
1function greet(name = "Guest") {
2 return `Hello, ${name}`;
3}
4
5greet(); // → "Hello, Guest" undefined → default applies
6greet(undefined); // → "Hello, Guest" same thing, explicitly
7greet(null); // → "Hello, null" null is a value, no default
8
9// Same rule when destructuring:
10const { city = "Chennai" } = { city: null };
11city; // → null, not "Chennai"
12
13// The fix when your API sends null for "no value":
14const { city: rawCity } = response;
15const city = rawCity ?? "Chennai"; // → "Chennai"This is a real-world problem, not a puzzle. Most REST APIs and every SQL-backed backend represent a missing column as null, so data arriving from the server is full of nulls while your defaults are waiting for undefined. Normalising at the boundary — converting nulls to undefined as data enters your app, or reaching for ?? — saves a lot of confused debugging.
JSON treats them completely differently
JSON has null and has no concept of undefined at all. That asymmetry produces behaviour that looks arbitrary until you know the rule: `JSON.stringify` drops undefined values from objects, converts them to null inside arrays, and returns undefined entirely if you pass it undefined at the top level.
1JSON.stringify({ a: 1, b: undefined, c: null });
2// → '{"a":1,"c":null}' b vanished completely
3
4JSON.stringify([1, undefined, null]);
5// → '[1,null,null]' array indexes must be preserved
6
7JSON.stringify(undefined);
8// → undefined not a string at all
9
10JSON.stringify({ fn: () => {} , sym: Symbol("x") });
11// → '{}' functions and symbols vanish too
12
13JSON.parse('{"a":null}');
14// → { a: null } null survives the round trip intactThe practical consequence: if you PATCH an object to an API and set a field to undefined intending to clear it, the field disappears from the request body and the server sees no instruction at all. Set it to null and the server receives an explicit "set this to empty". This distinction is the entire basis of how partial updates work in most APIs.
In TypeScript, the difference is enforced
TypeScript treats null and undefined as two separate types, and under `strictNullChecks` neither is assignable to anything else. That turns a runtime surprise into a compile error, and it forces the question the JavaScript version lets you dodge: which one does this value actually use?
1let a: string = null; // → Error under strictNullChecks
2let b: string | null = null; // → fine
3
4interface User {
5 name: string;
6 nickname?: string; // → string | undefined (may be absent)
7 deletedAt: Date | null; // → present, explicitly empty
8}
9
10function find(id: string): User | undefined {} // "not found"
11function getDeleted(u: User): Date | null {} // "not deleted"
12
13// Narrowing handles both at once:
14function label(u: User) {
15 if (u.nickname == null) return u.name; // → narrows out null AND undefined
16 return u.nickname; // → string
17}The convention most TypeScript codebases settle on is worth stating in an interview: use undefined for "this may be absent" and null for "this is present and deliberately empty". An optional property is undefined; a database column that is nullable is null. Some teams go further and ban null entirely, which is the position the TypeScript compiler's own codebase takes.
So which should you use?
Never assign undefined by hand — let the engine produce it. Use null when you need to say "this is empty and I meant it", particularly for values that were once set and are now cleared. Be consistent within a codebase, and normalise data at the boundary so you are not testing for both everywhere.
| Situation | Use | Why |
|---|---|---|
| Optional function parameter | undefined | Defaults fire on it |
| Optional object property | undefined | Matches TypeScript's `?:` |
| Cleared / reset value | null | Distinguishes "cleared" from "never set" |
| Field to blank out in a PATCH | null | undefined is dropped by JSON.stringify |
| "Not found" from a lookup | undefined | Matches Map.get and Array.find |
| Nullable database column | null | Matches what the backend sends |
| Checking for either | == null or ?? | One operator, no coercion surprises |
Is undefined a keyword?
No — it is a property of the global object, which is why in old sloppy-mode code you could shadow it with a local variable called undefined. It has been read-only globally since ES5, but a function parameter named undefined can still shadow it. null is a genuine literal and cannot be reassigned at all.
What is the difference between undefined and "not defined"?
undefined means the variable exists but holds no value. "not defined" means there is no such binding at all, and reading it throws a ReferenceError. typeof is the exception: typeof someUndeclaredThing returns "undefined" instead of throwing, which is the one safe way to test for a variable that might not exist.
What is the temporal dead zone?
The period between entering a scope and reaching a let or const declaration. The binding exists but reading it throws a ReferenceError rather than giving undefined. var declarations are hoisted and initialised to undefined instead, which is the main behavioural difference between the two.
Why is Number(null) 0 but Number(undefined) NaN?
The specification defines it that way: null converts to 0 and undefined converts to NaN. It follows the same logic as everything else here — null is an empty value, and empty numerically is zero, while undefined is no value at all, which cannot be a number. It is why null >= 0 is true and undefined >= 0 is false.
How do I delete a property versus setting it to null?
delete obj.key removes the key entirely, so "key" in obj becomes false and Object.keys no longer lists it. obj.key = null keeps the key with an empty value. Setting to undefined is a third state: the key remains but is dropped by JSON.stringify, which is usually not what you want.
Frequently asked questions
- What is the difference between null and undefined in JavaScript?
- undefined means a value was never assigned — an uninitialised variable, a missing property, a function with no return. null is a value you assign deliberately to mean "empty". The engine produces undefined on its own; it never produces null, so every null in a program was written by a developer.
- Why does typeof null return "object"?
- A bug in the original 1995 implementation. Values carried a type tag, the tag for objects was 000, and null was the all-zeroes null pointer — so typeof read it as an object. Fixing it was proposed for ES6 and rejected because too much existing code depends on the current behaviour.
- Is null equal to undefined?
- With loose equality, yes: null == undefined is true. With strict equality, no: null === undefined is false, because they are different types. Notably null is not loosely equal to 0, "" or false — it is only equal to undefined and to itself.
- Should I use null or undefined in my code?
- Never assign undefined by hand — let the engine produce it. Use null when you want to say a value is deliberately empty, especially for something that was previously set and has been cleared. Be consistent, and normalise incoming API data so you are not checking for both everywhere.
- What is the difference between null and undefined in TypeScript?
- They are separate types, and under strictNullChecks neither is assignable to other types. The usual convention is undefined for "may be absent" — which is what an optional property `field?:` means — and null for "present but deliberately empty", which is what a nullable database column means.
- Why doesn't my default parameter work when I pass null?
- Default parameters and destructuring defaults only trigger on undefined. Passing null is passing a value, so the default is skipped. Since most APIs send null for missing fields, use ?? to supply the fallback instead of relying on the parameter default.
- What does x == null actually check?
- Exactly null and undefined, and nothing else. The specification gives loose equality a special rule for these two values that skips numeric coercion, so 0, "" and false do not match. It is the one genuinely useful, non-surprising use of ==.
- What is the difference between ?? and ||?
- || falls back on any falsy value, which includes 0, "", NaN and false. ?? falls back only on null and undefined. Use ?? when zero or an empty string are legitimate values that should be kept — which is most of the time when you are defaulting user data.
- What happens to undefined in JSON.stringify?
- It is dropped from objects, converted to null inside arrays because indexes must be preserved, and returns undefined rather than a string if you pass it at the top level. null, by contrast, round-trips intact. This is why a PATCH that clears a field must send null, not undefined.
- What is the difference between undefined and not defined?
- undefined means the variable exists but has no value. Not defined means there is no such variable, and reading it throws a ReferenceError. The one exception is typeof, which returns "undefined" for an undeclared name instead of throwing.
- Why is Number(null) 0 but Number(undefined) NaN?
- The language specifies it: null converts to 0, undefined converts to NaN. It also explains an odd pair of results — null >= 0 is true because relational operators coerce null to 0, while null == 0 is false because loose equality has a special rule that skips that coercion.
- How do I check if a variable is null or undefined?
- value == null covers both in one comparison with no coercion surprises. If your lint rules ban ==, write value === null || value === undefined, or restructure with ?? and ?. which already treat the two identically.
This is 1 of 80+ questions in the JavaScript Interview Kit
Get every question with detailed answers, follow-ups and real code — plus coding challenges and a last-minute revision sheet. One-time payment, instant access.
⚡ Get the JavaScript Interview Kit → ₹299