JavaScript is a dynamically and weakly typed language — you never declare a type for a variable, and values can be implicitly coerced between types. This flexibility makes JavaScript approachable but also introduces subtleties that every developer needs to understand.
Modern JavaScript provides three ways to declare variables: let, const, and the legacy var. Each has different scoping and reassignment rules that affect how your programs behave. Understanding these differences is one of the first steps toward writing reliable JavaScript.
In this tutorial you’ll learn how to declare variables, explore JavaScript’s primitive and object types, work with type checking and conversions, and understand the distinction between null and undefined.
Variable Declarations: let, const, and var
JavaScript’s three declaration keywords serve different purposes. Modern code favors let and const, while var remains for legacy compatibility.
Create a file named variables.js:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
| // --- const: for values that won't be reassigned ---
const PI = 3.14159;
const APP_NAME = "CodeArchaeology";
const LANGUAGES = ["JavaScript", "Python", "Rust"];
console.log("=== const declarations ===");
console.log("PI:", PI);
console.log("APP_NAME:", APP_NAME);
console.log("LANGUAGES:", LANGUAGES);
// const objects and arrays can still be mutated
LANGUAGES.push("Go");
console.log("After push:", LANGUAGES);
// --- let: for values that will change ---
console.log("\n=== let declarations ===");
let counter = 0;
console.log("counter:", counter);
counter = counter + 1;
console.log("counter after increment:", counter);
let message;
console.log("uninitialized let:", message);
message = "now assigned";
console.log("after assignment:", message);
// --- var: legacy declaration (function-scoped) ---
console.log("\n=== var vs let scoping ===");
if (true) {
var varScoped = "I leak out of blocks";
let letScoped = "I stay in this block";
}
console.log("var from block:", varScoped);
// console.log(letScoped); // Would throw ReferenceError
// --- var hoisting ---
console.log("hoisted var:", hoisted);
var hoisted = "defined later";
console.log("after assignment:", hoisted);
|
Primitive Types
JavaScript has seven primitive types. They are immutable and compared by value.
Create a file named types.js:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
| // --- Number: 64-bit floating point (IEEE 754) ---
console.log("=== Numbers ===");
const integer = 42;
const float = 3.14;
const negative = -17;
const scientific = 2.998e8;
const hex = 0xff;
const binary = 0b1010;
const octal = 0o777;
console.log("integer:", integer, "| type:", typeof integer);
console.log("float:", float, "| type:", typeof float);
console.log("scientific:", scientific);
console.log("hex:", hex, "binary:", binary, "octal:", octal);
// Special numeric values
console.log("Infinity:", 1 / 0);
console.log("-Infinity:", -1 / 0);
console.log("NaN:", 0 / 0);
console.log("NaN === NaN:", NaN === NaN);
console.log("Number.isNaN(NaN):", Number.isNaN(NaN));
// --- BigInt: arbitrary-precision integers ---
console.log("\n=== BigInt ===");
const big = 9007199254740993n;
console.log("BigInt:", big, "| type:", typeof big);
// --- String ---
console.log("\n=== Strings ===");
const single = 'single quotes';
const double = "double quotes";
const template = `template literal: 2 + 2 = ${2 + 2}`;
const multiline = `line one
line two`;
console.log(single);
console.log(double);
console.log(template);
console.log("multiline:", multiline);
console.log("length:", single.length);
console.log("charAt(0):", single.charAt(0));
console.log("includes('single'):", single.includes("single"));
// --- Boolean ---
console.log("\n=== Booleans ===");
const isActive = true;
const isDeleted = false;
console.log("isActive:", isActive, "| type:", typeof isActive);
// --- undefined and null ---
console.log("\n=== undefined and null ===");
let notAssigned;
const explicitNull = null;
console.log("notAssigned:", notAssigned, "| type:", typeof notAssigned);
console.log("explicitNull:", explicitNull, "| type:", typeof explicitNull);
console.log("null == undefined:", null == undefined);
console.log("null === undefined:", null === undefined);
// --- Symbol: unique identifiers ---
console.log("\n=== Symbol ===");
const sym1 = Symbol("description");
const sym2 = Symbol("description");
console.log("sym1:", sym1.toString(), "| type:", typeof sym1);
console.log("sym1 === sym2:", sym1 === sym2);
|
Type Coercion and Conversions
JavaScript’s weak typing means values are implicitly converted in many contexts. Understanding these rules helps avoid subtle bugs.
Create a file named conversions.js:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
| // --- Implicit coercion (type coercion) ---
console.log("=== Implicit Coercion ===");
console.log('"5" + 3:', "5" + 3); // "53" (string concatenation)
console.log('"5" - 3:', "5" - 3); // 2 (numeric subtraction)
console.log('"5" * 2:', "5" * 2); // 10
console.log('true + 1:', true + 1); // 2
console.log('false + 1:', false + 1); // 1
console.log('"" == false:', "" == false); // true
console.log('0 == false:', 0 == false); // true
// --- Explicit conversions ---
console.log("\n=== Explicit Conversions ===");
// To number
console.log('Number("42"):', Number("42"));
console.log('Number("3.14"):', Number("3.14"));
console.log('Number(""):', Number(""));
console.log('Number("hello"):', Number("hello"));
console.log('parseInt("42px"):', parseInt("42px"));
console.log('parseFloat("3.14em"):', parseFloat("3.14em"));
// To string
console.log('String(42):', String(42));
console.log('String(true):', String(true));
console.log('String(null):', String(null));
console.log('String(undefined):', String(undefined));
console.log('(42).toString():', (42).toString());
console.log('(255).toString(16):', (255).toString(16));
// To boolean — falsy values (everything else is truthy)
console.log("\n=== Truthy and Falsy ===");
console.log("Falsy values (all convert to false):");
console.log(" Boolean(false):", Boolean(false));
console.log(" Boolean(0):", Boolean(0));
console.log(" Boolean(''):", Boolean(""));
console.log(" Boolean(null):", Boolean(null));
console.log(" Boolean(undefined):", Boolean(undefined));
console.log(" Boolean(NaN):", Boolean(NaN));
console.log("Truthy surprises:");
console.log(' Boolean("0"):', Boolean("0")); // true — non-empty string!
console.log(' Boolean("false"):', Boolean("false")); // true — non-empty string!
console.log(" Boolean([]):", Boolean([])); // true — empty array!
console.log(" Boolean({}):", Boolean({})); // true — empty object!
// --- typeof operator ---
console.log("\n=== typeof ===");
console.log('typeof 42:', typeof 42);
console.log('typeof "hi":', typeof "hi");
console.log('typeof true:', typeof true);
console.log('typeof undefined:', typeof undefined);
console.log('typeof null:', typeof null); // "object" — a famous bug!
console.log('typeof Symbol():', typeof Symbol());
console.log('typeof 42n:', typeof 42n);
console.log('typeof {}:', typeof {});
console.log('typeof []:', typeof []);
console.log('Array.isArray([]):', Array.isArray([]));
|
Running with Docker
1
2
3
4
5
6
7
8
9
10
11
| # Pull the official Node.js Alpine image
docker pull node:22-alpine
# Run the variables example
docker run --rm -v $(pwd):/app -w /app node:22-alpine node variables.js
# Run the types example
docker run --rm -v $(pwd):/app -w /app node:22-alpine node types.js
# Run the conversions example
docker run --rm -v $(pwd):/app -w /app node:22-alpine node conversions.js
|
Expected Output
Output from variables.js:
=== const declarations ===
PI: 3.14159
APP_NAME: CodeArchaeology
LANGUAGES: [ 'JavaScript', 'Python', 'Rust' ]
After push: [ 'JavaScript', 'Python', 'Rust', 'Go' ]
=== let declarations ===
counter: 0
counter after increment: 1
uninitialized let: undefined
after assignment: now assigned
=== var vs let scoping ===
var from block: I leak out of blocks
hoisted var: undefined
after assignment: defined later
Output from types.js:
=== Numbers ===
integer: 42 | type: number
float: 3.14 | type: number
scientific: 299800000
hex: 255 binary: 10 octal: 511
Infinity: Infinity
-Infinity: -Infinity
NaN: NaN
NaN === NaN: false
Number.isNaN(NaN): true
=== BigInt ===
BigInt: 9007199254740993n | type: bigint
=== Strings ===
single quotes
double quotes
template literal: 2 + 2 = 4
multiline: line one
line two
length: 13
charAt(0): s
includes('single'): true
=== Booleans ===
isActive: true | type: boolean
=== undefined and null ===
notAssigned: undefined | type: undefined
explicitNull: null | type: object
null == undefined: true
null === undefined: false
=== Symbol ===
sym1: Symbol(description) | type: symbol
sym1 === sym2: false
Output from conversions.js:
=== Implicit Coercion ===
"5" + 3: 53
"5" - 3: 2
"5" * 2: 10
true + 1: 2
false + 1: 1
"" == false: true
0 == false: true
=== Explicit Conversions ===
Number("42"): 42
Number("3.14"): 3.14
Number(""): 0
Number("hello"): NaN
parseInt("42px"): 42
parseFloat("3.14em"): 3.14
String(42): 42
String(true): true
String(null): null
String(undefined): undefined
(42).toString(): 42
(255).toString(16): ff
=== Truthy and Falsy ===
Falsy values (all convert to false):
Boolean(false): false
Boolean(0): false
Boolean(''): false
Boolean(null): false
Boolean(undefined): false
Boolean(NaN): false
Truthy surprises:
Boolean("0"): true
Boolean("false"): true
Boolean([]): true
Boolean({}): true
=== typeof ===
typeof 42: number
typeof "hi": string
typeof true: boolean
typeof undefined: undefined
typeof null: object
typeof Symbol(): symbol
typeof 42n: bigint
typeof {}: object
typeof []: object
Array.isArray([]): true
Key Concepts
- Use
const by default, switch to let only when you need to reassign a value, and avoid var in modern code - JavaScript has seven primitive types: number, string, boolean, undefined, null, symbol, and bigint — everything else is an object
- Dynamic typing means any variable can hold any type and change types at runtime — there are no type declarations
- Weak typing causes implicit coercion:
"5" + 3 produces "53" (string) but "5" - 3 produces 2 (number) typeof null returns "object" — this is a well-known language bug from the original implementation that can never be fixed for backward compatibility- Truthy/falsy values determine how non-booleans behave in boolean contexts:
0, "", null, undefined, and NaN are all falsy - Always use
=== (strict equality) instead of == (loose equality) to avoid unexpected type coercion in comparisons
Comments
Loading comments...
Leave a Comment